├── .editorconfig ├── src └── Rules │ ├── Operators │ ├── OperandInArithmeticPreDecrementRule.php │ ├── OperandInArithmeticPreIncrementRule.php │ ├── OperandInArithmeticPostDecrementRule.php │ ├── OperandInArithmeticPostIncrementRule.php │ ├── OperandInArithmeticUnaryPlusRule.php │ ├── OperandInArithmeticUnaryMinusRule.php │ ├── OperandInArithmeticIncrementOrDecrementRule.php │ ├── OperandsInArithmeticModuloRule.php │ ├── OperandsInArithmeticDivisionRule.php │ ├── OperandsInArithmeticExponentiationRule.php │ ├── OperandsInArithmeticMultiplicationRule.php │ ├── OperandsInArithmeticSubtractionRule.php │ ├── OperandsInArithmeticAdditionRule.php │ └── OperatorRuleHelper.php │ ├── DisallowedConstructs │ ├── DisallowedEmptyRule.php │ ├── DisallowedBacktickRule.php │ ├── DisallowedShortTernaryRule.php │ ├── DisallowedLooseComparisonRule.php │ └── DisallowedImplicitArrayCreationRule.php │ ├── VariableVariables │ ├── VariableVariablesRule.php │ ├── VariableMethodCallRule.php │ ├── VariableMethodCallableRule.php │ ├── VariableStaticMethodCallRule.php │ ├── VariableStaticPropertyFetchRule.php │ ├── VariableStaticMethodCallableRule.php │ └── VariablePropertyFetchRule.php │ ├── Methods │ ├── IllegalConstructorMethodCallRule.php │ ├── WrongCaseOfInheritedMethodRule.php │ └── IllegalConstructorStaticCallRule.php │ ├── BooleansInConditions │ ├── BooleanRuleHelper.php │ ├── BooleanInIfConditionRule.php │ ├── BooleanInDoWhileConditionRule.php │ ├── BooleanInWhileConditionRule.php │ ├── BooleanInBooleanNotRule.php │ ├── BooleanInElseIfConditionRule.php │ ├── BooleanInTernaryOperatorRule.php │ ├── BooleanInBooleanOrRule.php │ └── BooleanInBooleanAndRule.php │ ├── Functions │ ├── ClosureUsesThisRule.php │ └── ArrayFilterStrictRule.php │ ├── SwitchConditions │ └── MatchingTypeInSwitchCaseConditionRule.php │ ├── StrictCalls │ ├── DynamicCallOnStaticMethodsCallableRule.php │ ├── DynamicCallOnStaticMethodsRule.php │ └── StrictFunctionCallsRule.php │ ├── ForLoop │ └── OverwriteVariablesWithForLoopInitRule.php │ ├── ForeachLoop │ └── OverwriteVariablesWithForeachRule.php │ ├── Cast │ └── UselessCastRule.php │ └── Classes │ └── RequireParentConstructCallRule.php ├── composer.json ├── LICENSE ├── README.md └── rules.neon /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | 9 | [*.{php,phpt}] 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.xml] 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [*.neon] 18 | indent_style = tab 19 | indent_size = 4 20 | 21 | [*.{yaml,yml}] 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [composer.json] 26 | indent_style = tab 27 | indent_size = 4 28 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticPreDecrementRule.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OperandInArithmeticPreDecrementRule extends OperandInArithmeticIncrementOrDecrementRule 11 | { 12 | 13 | public function getNodeType(): string 14 | { 15 | return PreDec::class; 16 | } 17 | 18 | protected function describeOperation(): string 19 | { 20 | return 'pre-decrement'; 21 | } 22 | 23 | protected function getIdentifier(): string 24 | { 25 | return 'preDec'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticPreIncrementRule.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OperandInArithmeticPreIncrementRule extends OperandInArithmeticIncrementOrDecrementRule 11 | { 12 | 13 | public function getNodeType(): string 14 | { 15 | return PreInc::class; 16 | } 17 | 18 | protected function describeOperation(): string 19 | { 20 | return 'pre-increment'; 21 | } 22 | 23 | protected function getIdentifier(): string 24 | { 25 | return 'preInc'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticPostDecrementRule.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OperandInArithmeticPostDecrementRule extends OperandInArithmeticIncrementOrDecrementRule 11 | { 12 | 13 | public function getNodeType(): string 14 | { 15 | return PostDec::class; 16 | } 17 | 18 | protected function describeOperation(): string 19 | { 20 | return 'post-decrement'; 21 | } 22 | 23 | protected function getIdentifier(): string 24 | { 25 | return 'postDec'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticPostIncrementRule.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class OperandInArithmeticPostIncrementRule extends OperandInArithmeticIncrementOrDecrementRule 11 | { 12 | 13 | public function getNodeType(): string 14 | { 15 | return PostInc::class; 16 | } 17 | 18 | protected function describeOperation(): string 19 | { 20 | return 'post-increment'; 21 | } 22 | 23 | protected function getIdentifier(): string 24 | { 25 | return 'postInc'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Rules/DisallowedConstructs/DisallowedEmptyRule.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DisallowedEmptyRule implements Rule 15 | { 16 | 17 | public function getNodeType(): string 18 | { 19 | return Empty_::class; 20 | } 21 | 22 | public function processNode(Node $node, Scope $scope): array 23 | { 24 | return [ 25 | RuleErrorBuilder::message('Construct empty() is not allowed. Use more strict comparison.') 26 | ->identifier('empty.notAllowed') 27 | ->build(), 28 | ]; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Rules/DisallowedConstructs/DisallowedBacktickRule.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DisallowedBacktickRule implements Rule 15 | { 16 | 17 | public function getNodeType(): string 18 | { 19 | return ShellExec::class; 20 | } 21 | 22 | public function processNode(Node $node, Scope $scope): array 23 | { 24 | return [ 25 | RuleErrorBuilder::message('Backtick operator is not allowed. Use shell_exec() instead.') 26 | ->identifier('backtick.notAllowed') 27 | ->build(), 28 | ]; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableVariablesRule.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class VariableVariablesRule implements Rule 16 | { 17 | 18 | public function getNodeType(): string 19 | { 20 | return Variable::class; 21 | } 22 | 23 | public function processNode(Node $node, Scope $scope): array 24 | { 25 | if (is_string($node->name)) { 26 | return []; 27 | } 28 | 29 | return [ 30 | RuleErrorBuilder::message('Variable variables are not allowed.') 31 | ->identifier('variable.dynamicName') 32 | ->build(), 33 | ]; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/Rules/Methods/IllegalConstructorMethodCallRule.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class IllegalConstructorMethodCallRule implements Rule 14 | { 15 | 16 | public function getNodeType(): string 17 | { 18 | return Node\Expr\MethodCall::class; 19 | } 20 | 21 | public function processNode(Node $node, Scope $scope): array 22 | { 23 | if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { 24 | return []; 25 | } 26 | 27 | return [ 28 | RuleErrorBuilder::message('Call to __construct() on an existing object is not allowed.') 29 | ->identifier('constructor.call') 30 | ->build(), 31 | ]; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/Rules/DisallowedConstructs/DisallowedShortTernaryRule.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DisallowedShortTernaryRule implements Rule 15 | { 16 | 17 | public function getNodeType(): string 18 | { 19 | return Ternary::class; 20 | } 21 | 22 | public function processNode(Node $node, Scope $scope): array 23 | { 24 | if ($node->if !== null) { 25 | return []; 26 | } 27 | 28 | return [ 29 | RuleErrorBuilder::message('Short ternary operator is not allowed. Use null coalesce operator if applicable or consider using long ternary.') 30 | ->identifier('ternary.shortNotAllowed') 31 | ->build(), 32 | ]; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpstan/phpstan-strict-rules", 3 | "type": "phpstan-extension", 4 | "description": "Extra strict and opinionated rules for PHPStan", 5 | "license": [ 6 | "MIT" 7 | ], 8 | "require": { 9 | "php": "^7.4 || ^8.0", 10 | "phpstan/phpstan": "^2.1.29" 11 | }, 12 | "require-dev": { 13 | "php-parallel-lint/php-parallel-lint": "^1.2", 14 | "phpstan/phpstan-deprecation-rules": "^2.0", 15 | "phpstan/phpstan-phpunit": "^2.0", 16 | "phpunit/phpunit": "^9.6" 17 | }, 18 | "config": { 19 | "platform": { 20 | "php": "7.4.6" 21 | }, 22 | "sort-packages": true 23 | }, 24 | "extra": { 25 | "phpstan": { 26 | "includes": [ 27 | "rules.neon" 28 | ] 29 | } 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "PHPStan\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "classmap": [ 38 | "tests/" 39 | ] 40 | }, 41 | "minimum-stability": "dev", 42 | "prefer-stable": true 43 | } 44 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableMethodCallRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class VariableMethodCallRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return MethodCall::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->name instanceof Node\Identifier) { 27 | return []; 28 | } 29 | 30 | return [ 31 | RuleErrorBuilder::message(sprintf( 32 | 'Variable method call on %s.', 33 | $scope->getType($node->var)->describe(VerbosityLevel::typeOnly()), 34 | ))->identifier('method.dynamicName')->build(), 35 | ]; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableMethodCallableRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class VariableMethodCallableRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return MethodCallableNode::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->getName() instanceof Node\Identifier) { 27 | return []; 28 | } 29 | 30 | return [ 31 | RuleErrorBuilder::message(sprintf( 32 | 'Variable method call on %s.', 33 | $scope->getType($node->getVar())->describe(VerbosityLevel::typeOnly()), 34 | ))->identifier('method.dynamicName')->build(), 35 | ]; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanRuleHelper.php: -------------------------------------------------------------------------------- 1 | ruleLevelHelper = $ruleLevelHelper; 20 | } 21 | 22 | public function passesAsBoolean(Scope $scope, Expr $expr): bool 23 | { 24 | $type = $scope->getType($expr); 25 | if ($type instanceof MixedType) { 26 | return !$type->isExplicitMixed(); 27 | } 28 | $typeToCheck = $this->ruleLevelHelper->findTypeToCheck( 29 | $scope, 30 | $expr, 31 | '', 32 | static fn (Type $type): bool => $type->isBoolean()->yes(), 33 | ); 34 | $foundType = $typeToCheck->getType(); 35 | if ($foundType instanceof ErrorType) { 36 | return true; 37 | } 38 | 39 | return $foundType->isBoolean()->yes(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ondřej Mirtes 4 | Copyright (c) 2025 PHPStan s.r.o. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableStaticMethodCallRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class VariableStaticMethodCallRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return StaticCall::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->name instanceof Node\Identifier) { 27 | return []; 28 | } 29 | 30 | if ($node->class instanceof Node\Name) { 31 | $methodCalledOn = $scope->resolveName($node->class); 32 | } else { 33 | $methodCalledOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); 34 | } 35 | 36 | return [ 37 | RuleErrorBuilder::message(sprintf( 38 | 'Variable static method call on %s.', 39 | $methodCalledOn, 40 | ))->identifier('staticMethod.dynamicName')->build(), 41 | ]; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInIfConditionRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInIfConditionRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return If_::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | if ($this->helper->passesAsBoolean($scope, $node->cond)) { 34 | return []; 35 | } 36 | 37 | $conditionExpressionType = $scope->getType($node->cond); 38 | 39 | return [ 40 | RuleErrorBuilder::message(sprintf( 41 | 'Only booleans are allowed in an if condition, %s given.', 42 | $conditionExpressionType->describe(VerbosityLevel::typeOnly()), 43 | ))->identifier('if.condNotBoolean')->build(), 44 | ]; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInDoWhileConditionRule.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class BooleanInDoWhileConditionRule implements Rule 16 | { 17 | 18 | private BooleanRuleHelper $helper; 19 | 20 | public function __construct(BooleanRuleHelper $helper) 21 | { 22 | $this->helper = $helper; 23 | } 24 | 25 | public function getNodeType(): string 26 | { 27 | return Node\Stmt\Do_::class; 28 | } 29 | 30 | public function processNode(Node $node, Scope $scope): array 31 | { 32 | if ($this->helper->passesAsBoolean($scope, $node->cond)) { 33 | return []; 34 | } 35 | 36 | $conditionExpressionType = $scope->getType($node->cond); 37 | 38 | return [ 39 | RuleErrorBuilder::message(sprintf( 40 | 'Only booleans are allowed in a do-while condition, %s given.', 41 | $conditionExpressionType->describe(VerbosityLevel::typeOnly()), 42 | ))->identifier('doWhile.condNotBoolean')->build(), 43 | ]; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInWhileConditionRule.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class BooleanInWhileConditionRule implements Rule 16 | { 17 | 18 | private BooleanRuleHelper $helper; 19 | 20 | public function __construct(BooleanRuleHelper $helper) 21 | { 22 | $this->helper = $helper; 23 | } 24 | 25 | public function getNodeType(): string 26 | { 27 | return Node\Stmt\While_::class; 28 | } 29 | 30 | public function processNode(Node $node, Scope $scope): array 31 | { 32 | if ($this->helper->passesAsBoolean($scope, $node->cond)) { 33 | return []; 34 | } 35 | 36 | $conditionExpressionType = $scope->getType($node->cond); 37 | 38 | return [ 39 | RuleErrorBuilder::message(sprintf( 40 | 'Only booleans are allowed in a while condition, %s given.', 41 | $conditionExpressionType->describe(VerbosityLevel::typeOnly()), 42 | ))->identifier('while.condNotBoolean')->build(), 43 | ]; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableStaticPropertyFetchRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class VariableStaticPropertyFetchRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return StaticPropertyFetch::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->name instanceof Node\Identifier) { 27 | return []; 28 | } 29 | 30 | if ($node->class instanceof Node\Name) { 31 | $propertyAccessedOn = $scope->resolveName($node->class); 32 | } else { 33 | $propertyAccessedOn = $scope->getType($node->class)->describe(VerbosityLevel::typeOnly()); 34 | } 35 | 36 | return [ 37 | RuleErrorBuilder::message(sprintf( 38 | 'Variable static property access on %s.', 39 | $propertyAccessedOn, 40 | ))->identifier('staticProperty.dynamicName')->build(), 41 | ]; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInBooleanNotRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInBooleanNotRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return BooleanNot::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | if ($this->helper->passesAsBoolean($scope, $node->expr)) { 34 | return []; 35 | } 36 | 37 | $expressionType = $scope->getType($node->expr); 38 | 39 | return [ 40 | RuleErrorBuilder::message(sprintf( 41 | 'Only booleans are allowed in a negated boolean, %s given.', 42 | $expressionType->describe(VerbosityLevel::typeOnly()), 43 | ))->identifier('booleanNot.exprNotBoolean')->build(), 44 | ]; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariableStaticMethodCallableRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class VariableStaticMethodCallableRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return StaticMethodCallableNode::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->getName() instanceof Node\Identifier) { 27 | return []; 28 | } 29 | 30 | if ($node->getClass() instanceof Node\Name) { 31 | $methodCalledOn = $scope->resolveName($node->getClass()); 32 | } else { 33 | $methodCalledOn = $scope->getType($node->getClass())->describe(VerbosityLevel::typeOnly()); 34 | } 35 | 36 | return [ 37 | RuleErrorBuilder::message(sprintf( 38 | 'Variable static method call on %s.', 39 | $methodCalledOn, 40 | ))->identifier('staticMethod.dynamicName')->build(), 41 | ]; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInElseIfConditionRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInElseIfConditionRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return ElseIf_::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | if ($this->helper->passesAsBoolean($scope, $node->cond)) { 34 | return []; 35 | } 36 | 37 | $conditionExpressionType = $scope->getType($node->cond); 38 | 39 | return [ 40 | RuleErrorBuilder::message(sprintf( 41 | 'Only booleans are allowed in an elseif condition, %s given.', 42 | $conditionExpressionType->describe(VerbosityLevel::typeOnly()), 43 | ))->identifier('elseif.condNotBoolean')->build(), 44 | ]; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticUnaryPlusRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class OperandInArithmeticUnaryPlusRule implements Rule 17 | { 18 | 19 | private OperatorRuleHelper $helper; 20 | 21 | public function __construct(OperatorRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return UnaryPlus::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | $messages = []; 34 | 35 | if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) { 36 | $varType = $scope->getType($node->expr); 37 | 38 | $messages[] = RuleErrorBuilder::message(sprintf( 39 | 'Only numeric types are allowed in unary +, %s given.', 40 | $varType->describe(VerbosityLevel::typeOnly()), 41 | ))->identifier('unaryPlus.nonNumeric')->build(); 42 | } 43 | 44 | return $messages; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticUnaryMinusRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class OperandInArithmeticUnaryMinusRule implements Rule 17 | { 18 | 19 | private OperatorRuleHelper $helper; 20 | 21 | public function __construct(OperatorRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return UnaryMinus::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | $messages = []; 34 | 35 | if (!$this->helper->isValidForArithmeticOperation($scope, $node->expr)) { 36 | $varType = $scope->getType($node->expr); 37 | 38 | $messages[] = RuleErrorBuilder::message(sprintf( 39 | 'Only numeric types are allowed in unary -, %s given.', 40 | $varType->describe(VerbosityLevel::typeOnly()), 41 | ))->identifier('unaryMinus.nonNumeric')->build(); 42 | } 43 | 44 | return $messages; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/DisallowedConstructs/DisallowedLooseComparisonRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class DisallowedLooseComparisonRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return BinaryOp::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node instanceof Equal) { 27 | return [ 28 | RuleErrorBuilder::message( 29 | 'Loose comparison via "==" is not allowed.', 30 | )->tip('Use strict comparison via "===" instead.') 31 | ->identifier('equal.notAllowed') 32 | ->build(), 33 | ]; 34 | } 35 | if ($node instanceof NotEqual) { 36 | return [ 37 | RuleErrorBuilder::message( 38 | 'Loose comparison via "!=" is not allowed.', 39 | )->tip('Use strict comparison via "!==" instead.') 40 | ->identifier('notEqual.notAllowed') 41 | ->build(), 42 | ]; 43 | } 44 | 45 | return []; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInTernaryOperatorRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInTernaryOperatorRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return Ternary::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | if ($node->if === null) { 34 | return []; // elvis ?: 35 | } 36 | 37 | if ($this->helper->passesAsBoolean($scope, $node->cond)) { 38 | return []; 39 | } 40 | 41 | $conditionExpressionType = $scope->getType($node->cond); 42 | 43 | return [ 44 | RuleErrorBuilder::message(sprintf( 45 | 'Only booleans are allowed in a ternary operator condition, %s given.', 46 | $conditionExpressionType->describe(VerbosityLevel::typeOnly()), 47 | ))->identifier('ternary.condNotBoolean')->build(), 48 | ]; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Rules/Functions/ClosureUsesThisRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ClosureUsesThisRule implements Rule 17 | { 18 | 19 | public function getNodeType(): string 20 | { 21 | return Node\Expr\Closure::class; 22 | } 23 | 24 | public function processNode(Node $node, Scope $scope): array 25 | { 26 | if ($node->static) { 27 | return []; 28 | } 29 | 30 | if ($scope->isInClosureBind()) { 31 | return []; 32 | } 33 | 34 | $messages = []; 35 | foreach ($node->uses as $closureUse) { 36 | $varType = $scope->getType($closureUse->var); 37 | if (!is_string($closureUse->var->name)) { 38 | continue; 39 | } 40 | if (!$varType instanceof ThisType) { 41 | continue; 42 | } 43 | 44 | $messages[] = RuleErrorBuilder::message(sprintf('Anonymous function uses $this assigned to variable $%s. Use $this directly in the function body.', $closureUse->var->name)) 45 | ->line($closureUse->getStartLine()) 46 | ->identifier('closure.useThis') 47 | ->build(); 48 | } 49 | return $messages; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/Rules/SwitchConditions/MatchingTypeInSwitchCaseConditionRule.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class MatchingTypeInSwitchCaseConditionRule implements Rule 18 | { 19 | 20 | private Printer $printer; 21 | 22 | public function __construct(Printer $printer) 23 | { 24 | $this->printer = $printer; 25 | } 26 | 27 | public function getNodeType(): string 28 | { 29 | return Switch_::class; 30 | } 31 | 32 | public function processNode(Node $node, Scope $scope): array 33 | { 34 | $messages = []; 35 | $conditionType = $scope->getType($node->cond); 36 | foreach ($node->cases as $case) { 37 | if ($case->cond === null) { 38 | continue; 39 | } 40 | 41 | $caseType = $scope->getType($case->cond); 42 | if (!$conditionType->isSuperTypeOf($caseType)->no()) { 43 | continue; 44 | } 45 | 46 | $messages[] = RuleErrorBuilder::message(sprintf( 47 | 'Switch condition type (%s) does not match case condition %s (%s).', 48 | $conditionType->describe(VerbosityLevel::value()), 49 | $this->printer->prettyPrintExpr($case->cond), 50 | $caseType->describe(VerbosityLevel::typeOnly()), 51 | )) 52 | ->line($case->getStartLine()) 53 | ->identifier('switch.type') 54 | ->build(); 55 | } 56 | 57 | return $messages; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/Rules/DisallowedConstructs/DisallowedImplicitArrayCreationRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DisallowedImplicitArrayCreationRule implements Rule 19 | { 20 | 21 | public function getNodeType(): string 22 | { 23 | return Assign::class; 24 | } 25 | 26 | public function processNode(Node $node, Scope $scope): array 27 | { 28 | if (!$node->var instanceof ArrayDimFetch) { 29 | return []; 30 | } 31 | 32 | $node = $node->var; 33 | while ($node instanceof ArrayDimFetch) { 34 | $node = $node->var; 35 | } 36 | 37 | if (!$node instanceof Variable) { 38 | return []; 39 | } 40 | 41 | if (!is_string($node->name)) { 42 | return []; 43 | } 44 | 45 | $certainty = $scope->hasVariableType($node->name); 46 | if ($certainty->no()) { 47 | return [ 48 | RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s does not exist.', $node->name)) 49 | ->identifier('variable.implicitArray') 50 | ->build(), 51 | ]; 52 | } 53 | 54 | if ($certainty->maybe()) { 55 | return [ 56 | RuleErrorBuilder::message(sprintf('Implicit array creation is not allowed - variable $%s might not exist.', $node->name)) 57 | ->identifier('variable.implicitArray') 58 | ->build(), 59 | ]; 60 | } 61 | 62 | return []; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandInArithmeticIncrementOrDecrementRule.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | abstract class OperandInArithmeticIncrementOrDecrementRule implements Rule 21 | { 22 | 23 | private OperatorRuleHelper $helper; 24 | 25 | public function __construct(OperatorRuleHelper $helper) 26 | { 27 | $this->helper = $helper; 28 | } 29 | 30 | /** 31 | * @param TNodeType $node 32 | */ 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | $messages = []; 36 | $varType = $scope->getType($node->var); 37 | 38 | if ( 39 | ($node instanceof PreInc || $node instanceof PostInc) 40 | && !$this->helper->isValidForIncrement($scope, $node->var) 41 | || ($node instanceof PreDec || $node instanceof PostDec) 42 | && !$this->helper->isValidForDecrement($scope, $node->var) 43 | ) { 44 | $messages[] = RuleErrorBuilder::message(sprintf( 45 | 'Only numeric types are allowed in %s, %s given.', 46 | $this->describeOperation(), 47 | $varType->describe(VerbosityLevel::typeOnly()), 48 | ))->identifier(sprintf('%s.nonNumeric', $this->getIdentifier()))->build(); 49 | } 50 | 51 | return $messages; 52 | } 53 | 54 | abstract protected function describeOperation(): string; 55 | 56 | /** 57 | * @return 'preInc'|'postInc'|'preDec'|'postDec' 58 | */ 59 | abstract protected function getIdentifier(): string; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/Rules/StrictCalls/DynamicCallOnStaticMethodsCallableRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class DynamicCallOnStaticMethodsCallableRule implements Rule 19 | { 20 | 21 | private RuleLevelHelper $ruleLevelHelper; 22 | 23 | public function __construct(RuleLevelHelper $ruleLevelHelper) 24 | { 25 | $this->ruleLevelHelper = $ruleLevelHelper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return MethodCallableNode::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if (!$node->getName() instanceof Node\Identifier) { 36 | return []; 37 | } 38 | 39 | $name = $node->getName()->name; 40 | $type = $this->ruleLevelHelper->findTypeToCheck( 41 | $scope, 42 | $node->getVar(), 43 | '', 44 | static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), 45 | )->getType(); 46 | 47 | if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { 48 | return []; 49 | } 50 | 51 | $methodReflection = $type->getMethod($name, $scope); 52 | if ($methodReflection->isStatic()) { 53 | return [ 54 | RuleErrorBuilder::message(sprintf( 55 | 'Dynamic call to static method %s::%s().', 56 | $methodReflection->getDeclaringClass()->getDisplayName(), 57 | $methodReflection->getName(), 58 | ))->identifier('staticMethod.dynamicCall')->build(), 59 | ]; 60 | } 61 | 62 | return []; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/ForLoop/OverwriteVariablesWithForLoopInitRule.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class OverwriteVariablesWithForLoopInitRule implements Rule 20 | { 21 | 22 | public function getNodeType(): string 23 | { 24 | return For_::class; 25 | } 26 | 27 | public function processNode(Node $node, Scope $scope): array 28 | { 29 | $errors = []; 30 | foreach ($node->init as $expr) { 31 | if (!($expr instanceof Assign)) { 32 | continue; 33 | } 34 | 35 | foreach ($this->checkValueVar($scope, $expr->var) as $error) { 36 | $errors[] = $error; 37 | } 38 | } 39 | 40 | return $errors; 41 | } 42 | 43 | /** 44 | * @return list 45 | */ 46 | private function checkValueVar(Scope $scope, Expr $expr): array 47 | { 48 | $errors = []; 49 | if ( 50 | $expr instanceof Node\Expr\Variable 51 | && is_string($expr->name) 52 | && $scope->hasVariableType($expr->name)->yes() 53 | ) { 54 | $errors[] = RuleErrorBuilder::message(sprintf('For loop initial assignment overwrites variable $%s.', $expr->name)) 55 | ->identifier('for.variableOverwrite') 56 | ->build(); 57 | } 58 | 59 | if ( 60 | $expr instanceof Node\Expr\List_ 61 | || $expr instanceof Node\Expr\Array_ 62 | ) { 63 | foreach ($expr->items as $item) { 64 | if ($item === null) { 65 | continue; 66 | } 67 | 68 | foreach ($this->checkValueVar($scope, $item->value) as $error) { 69 | $errors[] = $error; 70 | } 71 | } 72 | } 73 | 74 | return $errors; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticModuloRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OperandsInArithmeticModuloRule implements Rule 19 | { 20 | 21 | private OperatorRuleHelper $helper; 22 | 23 | public function __construct(OperatorRuleHelper $helper) 24 | { 25 | $this->helper = $helper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return Expr::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if ($node instanceof BinaryOpMod) { 36 | $left = $node->left; 37 | $right = $node->right; 38 | } elseif ($node instanceof AssignOpMod) { 39 | $left = $node->var; 40 | $right = $node->expr; 41 | } else { 42 | return []; 43 | } 44 | 45 | $messages = []; 46 | $leftType = $scope->getType($left); 47 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 48 | $messages[] = RuleErrorBuilder::message(sprintf( 49 | 'Only numeric types are allowed in %%, %s given on the left side.', 50 | $leftType->describe(VerbosityLevel::typeOnly()), 51 | ))->identifier('mod.leftNonNumeric')->build(); 52 | } 53 | 54 | $rightType = $scope->getType($right); 55 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 56 | $messages[] = RuleErrorBuilder::message(sprintf( 57 | 'Only numeric types are allowed in %%, %s given on the right side.', 58 | $rightType->describe(VerbosityLevel::typeOnly()), 59 | ))->identifier('mod.rightNonNumeric')->build(); 60 | } 61 | 62 | return $messages; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticDivisionRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OperandsInArithmeticDivisionRule implements Rule 19 | { 20 | 21 | private OperatorRuleHelper $helper; 22 | 23 | public function __construct(OperatorRuleHelper $helper) 24 | { 25 | $this->helper = $helper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return Expr::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if ($node instanceof BinaryOpDiv) { 36 | $left = $node->left; 37 | $right = $node->right; 38 | } elseif ($node instanceof AssignOpDiv) { 39 | $left = $node->var; 40 | $right = $node->expr; 41 | } else { 42 | return []; 43 | } 44 | 45 | $messages = []; 46 | $leftType = $scope->getType($left); 47 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 48 | $messages[] = RuleErrorBuilder::message(sprintf( 49 | 'Only numeric types are allowed in /, %s given on the left side.', 50 | $leftType->describe(VerbosityLevel::typeOnly()), 51 | ))->identifier('div.leftNonNumeric')->build(); 52 | } 53 | 54 | $rightType = $scope->getType($right); 55 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 56 | $messages[] = RuleErrorBuilder::message(sprintf( 57 | 'Only numeric types are allowed in /, %s given on the right side.', 58 | $rightType->describe(VerbosityLevel::typeOnly()), 59 | ))->identifier('div.rightNonNumeric')->build(); 60 | } 61 | 62 | return $messages; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticExponentiationRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OperandsInArithmeticExponentiationRule implements Rule 19 | { 20 | 21 | private OperatorRuleHelper $helper; 22 | 23 | public function __construct(OperatorRuleHelper $helper) 24 | { 25 | $this->helper = $helper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return Expr::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if ($node instanceof BinaryOpPow) { 36 | $left = $node->left; 37 | $right = $node->right; 38 | } elseif ($node instanceof AssignOpPow) { 39 | $left = $node->var; 40 | $right = $node->expr; 41 | } else { 42 | return []; 43 | } 44 | 45 | $messages = []; 46 | $leftType = $scope->getType($left); 47 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 48 | $messages[] = RuleErrorBuilder::message(sprintf( 49 | 'Only numeric types are allowed in **, %s given on the left side.', 50 | $leftType->describe(VerbosityLevel::typeOnly()), 51 | ))->identifier('pow.leftNonNumeric')->build(); 52 | } 53 | 54 | $rightType = $scope->getType($right); 55 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 56 | $messages[] = RuleErrorBuilder::message(sprintf( 57 | 'Only numeric types are allowed in **, %s given on the right side.', 58 | $rightType->describe(VerbosityLevel::typeOnly()), 59 | ))->identifier('pow.rightNonNumeric')->build(); 60 | } 61 | 62 | return $messages; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticMultiplicationRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OperandsInArithmeticMultiplicationRule implements Rule 19 | { 20 | 21 | private OperatorRuleHelper $helper; 22 | 23 | public function __construct(OperatorRuleHelper $helper) 24 | { 25 | $this->helper = $helper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return Expr::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if ($node instanceof BinaryOpMul) { 36 | $left = $node->left; 37 | $right = $node->right; 38 | } elseif ($node instanceof AssignOpMul) { 39 | $left = $node->var; 40 | $right = $node->expr; 41 | } else { 42 | return []; 43 | } 44 | 45 | $messages = []; 46 | $leftType = $scope->getType($left); 47 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 48 | $messages[] = RuleErrorBuilder::message(sprintf( 49 | 'Only numeric types are allowed in *, %s given on the left side.', 50 | $leftType->describe(VerbosityLevel::typeOnly()), 51 | ))->identifier('mul.leftNonNumeric')->build(); 52 | } 53 | 54 | $rightType = $scope->getType($right); 55 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 56 | $messages[] = RuleErrorBuilder::message(sprintf( 57 | 'Only numeric types are allowed in *, %s given on the right side.', 58 | $rightType->describe(VerbosityLevel::typeOnly()), 59 | ))->identifier('mul.rightNonNumeric')->build(); 60 | } 61 | 62 | return $messages; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticSubtractionRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OperandsInArithmeticSubtractionRule implements Rule 19 | { 20 | 21 | private OperatorRuleHelper $helper; 22 | 23 | public function __construct(OperatorRuleHelper $helper) 24 | { 25 | $this->helper = $helper; 26 | } 27 | 28 | public function getNodeType(): string 29 | { 30 | return Expr::class; 31 | } 32 | 33 | public function processNode(Node $node, Scope $scope): array 34 | { 35 | if ($node instanceof BinaryOpMinus) { 36 | $left = $node->left; 37 | $right = $node->right; 38 | } elseif ($node instanceof AssignOpMinus) { 39 | $left = $node->var; 40 | $right = $node->expr; 41 | } else { 42 | return []; 43 | } 44 | 45 | $messages = []; 46 | $leftType = $scope->getType($left); 47 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 48 | $messages[] = RuleErrorBuilder::message(sprintf( 49 | 'Only numeric types are allowed in -, %s given on the left side.', 50 | $leftType->describe(VerbosityLevel::typeOnly()), 51 | ))->identifier('minus.leftNonNumeric')->build(); 52 | } 53 | 54 | $rightType = $scope->getType($right); 55 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 56 | $messages[] = RuleErrorBuilder::message(sprintf( 57 | 'Only numeric types are allowed in -, %s given on the right side.', 58 | $rightType->describe(VerbosityLevel::typeOnly()), 59 | ))->identifier('minus.rightNonNumeric')->build(); 60 | } 61 | 62 | return $messages; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInBooleanOrRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInBooleanOrRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return BooleanOrNode::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | $originalNode = $node->getOriginalNode(); 34 | $messages = []; 35 | $nodeText = $originalNode->getOperatorSigil(); 36 | $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanOr ? 'booleanOr' : 'logicalOr'; 37 | if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { 38 | $leftType = $scope->getType($originalNode->left); 39 | $messages[] = RuleErrorBuilder::message(sprintf( 40 | 'Only booleans are allowed in %s, %s given on the left side.', 41 | $nodeText, 42 | $leftType->describe(VerbosityLevel::typeOnly()), 43 | ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); 44 | } 45 | 46 | $rightScope = $node->getRightScope(); 47 | if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { 48 | $rightType = $rightScope->getType($originalNode->right); 49 | $messages[] = RuleErrorBuilder::message(sprintf( 50 | 'Only booleans are allowed in %s, %s given on the right side.', 51 | $nodeText, 52 | $rightType->describe(VerbosityLevel::typeOnly()), 53 | ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); 54 | } 55 | 56 | return $messages; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Rules/BooleansInConditions/BooleanInBooleanAndRule.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class BooleanInBooleanAndRule implements Rule 17 | { 18 | 19 | private BooleanRuleHelper $helper; 20 | 21 | public function __construct(BooleanRuleHelper $helper) 22 | { 23 | $this->helper = $helper; 24 | } 25 | 26 | public function getNodeType(): string 27 | { 28 | return BooleanAndNode::class; 29 | } 30 | 31 | public function processNode(Node $node, Scope $scope): array 32 | { 33 | $originalNode = $node->getOriginalNode(); 34 | $messages = []; 35 | $nodeText = $originalNode->getOperatorSigil(); 36 | $identifierType = $originalNode instanceof Node\Expr\BinaryOp\BooleanAnd ? 'booleanAnd' : 'logicalAnd'; 37 | if (!$this->helper->passesAsBoolean($scope, $originalNode->left)) { 38 | $leftType = $scope->getType($originalNode->left); 39 | $messages[] = RuleErrorBuilder::message(sprintf( 40 | 'Only booleans are allowed in %s, %s given on the left side.', 41 | $nodeText, 42 | $leftType->describe(VerbosityLevel::typeOnly()), 43 | ))->identifier(sprintf('%s.leftNotBoolean', $identifierType))->build(); 44 | } 45 | 46 | $rightScope = $node->getRightScope(); 47 | if (!$this->helper->passesAsBoolean($rightScope, $originalNode->right)) { 48 | $rightType = $rightScope->getType($originalNode->right); 49 | $messages[] = RuleErrorBuilder::message(sprintf( 50 | 'Only booleans are allowed in %s, %s given on the right side.', 51 | $nodeText, 52 | $rightType->describe(VerbosityLevel::typeOnly()), 53 | ))->identifier(sprintf('%s.rightNotBoolean', $identifierType))->build(); 54 | } 55 | 56 | return $messages; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperandsInArithmeticAdditionRule.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class OperandsInArithmeticAdditionRule implements Rule 20 | { 21 | 22 | private OperatorRuleHelper $helper; 23 | 24 | public function __construct(OperatorRuleHelper $helper) 25 | { 26 | $this->helper = $helper; 27 | } 28 | 29 | public function getNodeType(): string 30 | { 31 | return Expr::class; 32 | } 33 | 34 | public function processNode(Node $node, Scope $scope): array 35 | { 36 | if ($node instanceof BinaryOpPlus) { 37 | $left = $node->left; 38 | $right = $node->right; 39 | } elseif ($node instanceof AssignOpPlus) { 40 | $left = $node->var; 41 | $right = $node->expr; 42 | } else { 43 | return []; 44 | } 45 | 46 | $leftType = $scope->getType($left); 47 | $rightType = $scope->getType($right); 48 | if (count($leftType->getArrays()) > 0 && count($rightType->getArrays()) > 0) { 49 | return []; 50 | } 51 | 52 | $messages = []; 53 | if (!$this->helper->isValidForArithmeticOperation($scope, $left)) { 54 | $messages[] = RuleErrorBuilder::message(sprintf( 55 | 'Only numeric types are allowed in +, %s given on the left side.', 56 | $leftType->describe(VerbosityLevel::typeOnly()), 57 | ))->identifier('plus.leftNonNumeric')->build(); 58 | } 59 | if (!$this->helper->isValidForArithmeticOperation($scope, $right)) { 60 | $messages[] = RuleErrorBuilder::message(sprintf( 61 | 'Only numeric types are allowed in +, %s given on the right side.', 62 | $rightType->describe(VerbosityLevel::typeOnly()), 63 | ))->identifier('plus.rightNonNumeric')->build(); 64 | } 65 | 66 | return $messages; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/Rules/ForeachLoop/OverwriteVariablesWithForeachRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class OverwriteVariablesWithForeachRule implements Rule 19 | { 20 | 21 | public function getNodeType(): string 22 | { 23 | return Foreach_::class; 24 | } 25 | 26 | public function processNode(Node $node, Scope $scope): array 27 | { 28 | $errors = []; 29 | if ( 30 | $node->keyVar instanceof Node\Expr\Variable 31 | && is_string($node->keyVar->name) 32 | && $scope->hasVariableType($node->keyVar->name)->yes() 33 | ) { 34 | $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its key variable.', $node->keyVar->name)) 35 | ->identifier('foreach.keyOverwrite') 36 | ->build(); 37 | } 38 | 39 | foreach ($this->checkValueVar($scope, $node->valueVar) as $error) { 40 | $errors[] = $error; 41 | } 42 | 43 | return $errors; 44 | } 45 | 46 | /** 47 | * @return list 48 | */ 49 | private function checkValueVar(Scope $scope, Expr $expr): array 50 | { 51 | $errors = []; 52 | if ( 53 | $expr instanceof Node\Expr\Variable 54 | && is_string($expr->name) 55 | && $scope->hasVariableType($expr->name)->yes() 56 | ) { 57 | $errors[] = RuleErrorBuilder::message(sprintf('Foreach overwrites $%s with its value variable.', $expr->name)) 58 | ->identifier('foreach.valueOverwrite') 59 | ->build(); 60 | } 61 | 62 | if ( 63 | $expr instanceof Node\Expr\List_ 64 | || $expr instanceof Node\Expr\Array_ 65 | ) { 66 | foreach ($expr->items as $item) { 67 | if ($item === null) { 68 | continue; 69 | } 70 | 71 | foreach ($this->checkValueVar($scope, $item->value) as $error) { 72 | $errors[] = $error; 73 | } 74 | } 75 | } 76 | 77 | return $errors; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/Rules/StrictCalls/DynamicCallOnStaticMethodsRule.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class DynamicCallOnStaticMethodsRule implements Rule 22 | { 23 | 24 | private RuleLevelHelper $ruleLevelHelper; 25 | 26 | public function __construct(RuleLevelHelper $ruleLevelHelper) 27 | { 28 | $this->ruleLevelHelper = $ruleLevelHelper; 29 | } 30 | 31 | public function getNodeType(): string 32 | { 33 | return MethodCall::class; 34 | } 35 | 36 | public function processNode(Node $node, Scope $scope): array 37 | { 38 | if (!$node->name instanceof Node\Identifier) { 39 | return []; 40 | } 41 | 42 | $name = $node->name->name; 43 | $type = $this->ruleLevelHelper->findTypeToCheck( 44 | $scope, 45 | $node->var, 46 | '', 47 | static fn (Type $type): bool => $type->canCallMethods()->yes() && $type->hasMethod($name)->yes(), 48 | )->getType(); 49 | 50 | if ($type instanceof ErrorType || !$type->canCallMethods()->yes() || !$type->hasMethod($name)->yes()) { 51 | return []; 52 | } 53 | 54 | $methodReflection = $type->getMethod($name, $scope); 55 | if ($methodReflection->isStatic()) { 56 | $prototype = $methodReflection->getPrototype(); 57 | if (in_array($prototype->getDeclaringClass()->getName(), [ 58 | TypeInferenceTestCase::class, 59 | PHPStanTestCase::class, 60 | ], true)) { 61 | return []; 62 | } 63 | 64 | return [ 65 | RuleErrorBuilder::message(sprintf( 66 | 'Dynamic call to static method %s::%s().', 67 | $methodReflection->getDeclaringClass()->getDisplayName(), 68 | $methodReflection->getName(), 69 | ))->identifier('staticMethod.dynamicCall')->build(), 70 | ]; 71 | } 72 | 73 | return []; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/Rules/Methods/WrongCaseOfInheritedMethodRule.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class WrongCaseOfInheritedMethodRule implements Rule 18 | { 19 | 20 | public function getNodeType(): string 21 | { 22 | return InClassMethodNode::class; 23 | } 24 | 25 | public function processNode( 26 | Node $node, 27 | Scope $scope 28 | ): array 29 | { 30 | $methodReflection = $node->getMethodReflection(); 31 | $declaringClass = $methodReflection->getDeclaringClass(); 32 | 33 | $messages = []; 34 | if ($declaringClass->getParentClass() !== null) { 35 | $parentMessage = $this->findMethod( 36 | $declaringClass, 37 | $declaringClass->getParentClass(), 38 | $methodReflection->getName(), 39 | ); 40 | if ($parentMessage !== null) { 41 | $messages[] = $parentMessage; 42 | } 43 | } 44 | 45 | foreach ($declaringClass->getInterfaces() as $interface) { 46 | $interfaceMessage = $this->findMethod( 47 | $declaringClass, 48 | $interface, 49 | $methodReflection->getName(), 50 | ); 51 | if ($interfaceMessage === null) { 52 | continue; 53 | } 54 | 55 | $messages[] = $interfaceMessage; 56 | } 57 | 58 | return $messages; 59 | } 60 | 61 | private function findMethod( 62 | ClassReflection $declaringClass, 63 | ClassReflection $classReflection, 64 | string $methodName 65 | ): ?IdentifierRuleError 66 | { 67 | if (!$classReflection->hasNativeMethod($methodName)) { 68 | return null; 69 | } 70 | 71 | $parentMethod = $classReflection->getNativeMethod($methodName); 72 | if ($parentMethod->getName() === $methodName) { 73 | return null; 74 | } 75 | 76 | return RuleErrorBuilder::message(sprintf( 77 | 'Method %s::%s() does not match %s method name: %s::%s().', 78 | $declaringClass->getDisplayName(), 79 | $methodName, 80 | $classReflection->isInterface() ? 'interface' : 'parent', 81 | $classReflection->getDisplayName(), 82 | $parentMethod->getName(), 83 | ))->identifier('method.nameCase')->build(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Rules/Cast/UselessCastRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class UselessCastRule implements Rule 19 | { 20 | 21 | private bool $treatPhpDocTypesAsCertain; 22 | 23 | private bool $treatPhpDocTypesAsCertainTip; 24 | 25 | public function __construct( 26 | bool $treatPhpDocTypesAsCertain, 27 | bool $treatPhpDocTypesAsCertainTip 28 | ) 29 | { 30 | $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; 31 | $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; 32 | } 33 | 34 | public function getNodeType(): string 35 | { 36 | return Cast::class; 37 | } 38 | 39 | public function processNode(Node $node, Scope $scope): array 40 | { 41 | $castType = $scope->getType($node); 42 | if ($castType instanceof ErrorType) { 43 | return []; 44 | } 45 | $castType = $castType->generalize(GeneralizePrecision::lessSpecific()); 46 | 47 | if ($this->treatPhpDocTypesAsCertain) { 48 | $expressionType = $scope->getType($node->expr); 49 | } else { 50 | $expressionType = $scope->getNativeType($node->expr); 51 | } 52 | if ($castType->isSuperTypeOf($expressionType)->yes()) { 53 | $addTip = function (RuleErrorBuilder $ruleErrorBuilder) use ($scope, $node, $castType): RuleErrorBuilder { 54 | if (!$this->treatPhpDocTypesAsCertain) { 55 | return $ruleErrorBuilder; 56 | } 57 | 58 | if (!$this->treatPhpDocTypesAsCertainTip) { 59 | return $ruleErrorBuilder; 60 | } 61 | 62 | $expressionTypeWithoutPhpDoc = $scope->getNativeType($node->expr); 63 | if ($castType->isSuperTypeOf($expressionTypeWithoutPhpDoc)->yes()) { 64 | return $ruleErrorBuilder; 65 | } 66 | 67 | return $ruleErrorBuilder->treatPhpDocTypesAsCertainTip(); 68 | }; 69 | return [ 70 | $addTip(RuleErrorBuilder::message(sprintf( 71 | 'Casting to %s something that\'s already %s.', 72 | $castType->describe(VerbosityLevel::typeOnly()), 73 | $expressionType->describe(VerbosityLevel::typeOnly()), 74 | )))->identifier('cast.useless')->build(), 75 | ]; 76 | } 77 | 78 | return []; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/Rules/Operators/OperatorRuleHelper.php: -------------------------------------------------------------------------------- 1 | ruleLevelHelper = $ruleLevelHelper; 27 | } 28 | 29 | public function isValidForArithmeticOperation(Scope $scope, Expr $expr): bool 30 | { 31 | $type = $scope->getType($expr); 32 | if ($type instanceof MixedType) { 33 | return true; 34 | } 35 | 36 | // already reported by PHPStan core 37 | if ($type->toNumber() instanceof ErrorType) { 38 | return true; 39 | } 40 | 41 | return $this->isSubtypeOfNumber($scope, $expr); 42 | } 43 | 44 | public function isValidForIncrement(Scope $scope, Expr $expr): bool 45 | { 46 | $type = $scope->getType($expr); 47 | if ($type instanceof MixedType) { 48 | return true; 49 | } 50 | 51 | if ($type->isString()->yes()) { 52 | // Because `$a = 'a'; $a++;` is valid 53 | return true; 54 | } 55 | 56 | return $this->isSubtypeOfNumber($scope, $expr); 57 | } 58 | 59 | public function isValidForDecrement(Scope $scope, Expr $expr): bool 60 | { 61 | $type = $scope->getType($expr); 62 | if ($type instanceof MixedType) { 63 | return true; 64 | } 65 | 66 | return $this->isSubtypeOfNumber($scope, $expr); 67 | } 68 | 69 | private function isSubtypeOfNumber(Scope $scope, Expr $expr): bool 70 | { 71 | $acceptedType = new UnionType([new IntegerType(), new FloatType(), new IntersectionType([new StringType(), new AccessoryNumericStringType()])]); 72 | 73 | $type = $this->ruleLevelHelper->findTypeToCheck( 74 | $scope, 75 | $expr, 76 | '', 77 | static fn (Type $type): bool => $acceptedType->isSuperTypeOf($type)->yes(), 78 | )->getType(); 79 | 80 | if ($type instanceof ErrorType) { 81 | return true; 82 | } 83 | 84 | $isSuperType = $acceptedType->isSuperTypeOf($type); 85 | if ($type instanceof BenevolentUnionType) { 86 | return !$isSuperType->no(); 87 | } 88 | 89 | return $isSuperType->yes(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/Rules/VariableVariables/VariablePropertyFetchRule.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class VariablePropertyFetchRule implements Rule 20 | { 21 | 22 | private ReflectionProvider $reflectionProvider; 23 | 24 | /** @var string[] */ 25 | private array $universalObjectCratesClasses; 26 | 27 | /** 28 | * @param string[] $universalObjectCratesClasses 29 | */ 30 | public function __construct(ReflectionProvider $reflectionProvider, array $universalObjectCratesClasses) 31 | { 32 | $this->reflectionProvider = $reflectionProvider; 33 | $this->universalObjectCratesClasses = $universalObjectCratesClasses; 34 | } 35 | 36 | public function getNodeType(): string 37 | { 38 | return PropertyFetch::class; 39 | } 40 | 41 | public function processNode(Node $node, Scope $scope): array 42 | { 43 | if ($node->name instanceof Node\Identifier) { 44 | return []; 45 | } 46 | 47 | $fetchedOnType = $scope->getType($node->var); 48 | foreach ($fetchedOnType->getObjectClassNames() as $referencedClass) { 49 | if (!$this->reflectionProvider->hasClass($referencedClass)) { 50 | continue; 51 | } 52 | 53 | $classReflection = $this->reflectionProvider->getClass($referencedClass); 54 | if ( 55 | $this->isUniversalObjectCrate($classReflection) 56 | || $this->isSimpleXMLElement($classReflection) 57 | ) { 58 | return []; 59 | } 60 | } 61 | 62 | return [ 63 | RuleErrorBuilder::message(sprintf( 64 | 'Variable property access on %s.', 65 | $fetchedOnType->describe(VerbosityLevel::typeOnly()), 66 | ))->identifier('property.dynamicName')->build(), 67 | ]; 68 | } 69 | 70 | private function isSimpleXMLElement( 71 | ClassReflection $classReflection 72 | ): bool 73 | { 74 | return $classReflection->is(SimpleXMLElement::class); 75 | } 76 | 77 | private function isUniversalObjectCrate( 78 | ClassReflection $classReflection 79 | ): bool 80 | { 81 | foreach ($this->universalObjectCratesClasses as $className) { 82 | if (!$this->reflectionProvider->hasClass($className)) { 83 | continue; 84 | } 85 | 86 | if ($classReflection->is($className)) { 87 | return true; 88 | } 89 | } 90 | 91 | return false; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/Rules/Methods/IllegalConstructorStaticCallRule.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class IllegalConstructorStaticCallRule implements Rule 19 | { 20 | 21 | public function getNodeType(): string 22 | { 23 | return Node\Expr\StaticCall::class; 24 | } 25 | 26 | public function processNode(Node $node, Scope $scope): array 27 | { 28 | if (!$node->name instanceof Node\Identifier || $node->name->toLowerString() !== '__construct') { 29 | return []; 30 | } 31 | 32 | if ($this->isCollectCallingConstructor($node, $scope)) { 33 | return []; 34 | } 35 | 36 | return [ 37 | RuleErrorBuilder::message('Static call to __construct() is only allowed on a parent class in the constructor.') 38 | ->identifier('constructor.call') 39 | ->build(), 40 | ]; 41 | } 42 | 43 | private function isCollectCallingConstructor(Node\Expr\StaticCall $node, Scope $scope): bool 44 | { 45 | // __construct should be called from inside constructor 46 | if ($scope->getFunction() === null) { 47 | return false; 48 | } 49 | 50 | if ($scope->getFunction()->getName() !== '__construct') { 51 | if (!$this->isInRenamedTraitConstructor($scope)) { 52 | return false; 53 | } 54 | } 55 | 56 | if (!$scope->isInClass()) { 57 | return false; 58 | } 59 | 60 | if (!$node->class instanceof Node\Name) { 61 | return false; 62 | } 63 | 64 | $parentClasses = array_map(static fn (string $name) => strtolower($name), $scope->getClassReflection()->getParentClassesNames()); 65 | 66 | return in_array(strtolower($scope->resolveName($node->class)), $parentClasses, true); 67 | } 68 | 69 | private function isInRenamedTraitConstructor(Scope $scope): bool 70 | { 71 | if (!$scope->isInClass()) { 72 | return false; 73 | } 74 | 75 | if (!$scope->isInTrait()) { 76 | return false; 77 | } 78 | 79 | if ($scope->getFunction() === null) { 80 | return false; 81 | } 82 | 83 | $traitAliases = $scope->getClassReflection()->getNativeReflection()->getTraitAliases(); 84 | $functionName = $scope->getFunction()->getName(); 85 | if (!array_key_exists($functionName, $traitAliases)) { 86 | return false; 87 | } 88 | 89 | return $traitAliases[$functionName] === sprintf('%s::%s', $scope->getTraitReflection()->getName(), '__construct'); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/Rules/StrictCalls/StrictFunctionCallsRule.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class StrictFunctionCallsRule implements Rule 23 | { 24 | 25 | /** @var int[] */ 26 | private array $functionArguments = [ 27 | 'in_array' => 2, 28 | 'array_search' => 2, 29 | 'base64_decode' => 1, 30 | 'array_keys' => 2, 31 | ]; 32 | 33 | private ReflectionProvider $reflectionProvider; 34 | 35 | public function __construct(ReflectionProvider $reflectionProvider) 36 | { 37 | $this->reflectionProvider = $reflectionProvider; 38 | } 39 | 40 | public function getNodeType(): string 41 | { 42 | return FuncCall::class; 43 | } 44 | 45 | public function processNode(Node $node, Scope $scope): array 46 | { 47 | if (!$node->name instanceof Name) { 48 | return []; 49 | } 50 | 51 | if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { 52 | return []; 53 | } 54 | 55 | $function = $this->reflectionProvider->getFunction($node->name, $scope); 56 | $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs($scope, $node->getArgs(), $function->getVariants()); 57 | $node = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); 58 | if ($node === null) { 59 | return []; 60 | } 61 | $functionName = strtolower($function->getName()); 62 | if (!array_key_exists($functionName, $this->functionArguments)) { 63 | return []; 64 | } 65 | 66 | if ($functionName === 'array_keys' && !array_key_exists(1, $node->getArgs())) { 67 | return []; 68 | } 69 | 70 | $argumentPosition = $this->functionArguments[$functionName]; 71 | if (!array_key_exists($argumentPosition, $node->getArgs())) { 72 | return [ 73 | RuleErrorBuilder::message(sprintf( 74 | 'Call to function %s() requires parameter #%d to be set.', 75 | $functionName, 76 | $argumentPosition + 1, 77 | ))->identifier('function.strict')->build(), 78 | ]; 79 | } 80 | 81 | $argumentType = $scope->getType($node->getArgs()[$argumentPosition]->value); 82 | $trueType = new ConstantBooleanType(true); 83 | if (!$trueType->isSuperTypeOf($argumentType)->yes()) { 84 | return [ 85 | RuleErrorBuilder::message(sprintf( 86 | 'Call to function %s() requires parameter #%d to be true.', 87 | $functionName, 88 | $argumentPosition + 1, 89 | ))->identifier('function.strict')->build(), 90 | ]; 91 | } 92 | 93 | return []; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/Rules/Classes/RequireParentConstructCallRule.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class RequireParentConstructCallRule implements Rule 22 | { 23 | 24 | public function getNodeType(): string 25 | { 26 | return ClassMethod::class; 27 | } 28 | 29 | public function processNode(Node $node, Scope $scope): array 30 | { 31 | if (!$scope->isInClass()) { 32 | throw new ShouldNotHappenException(); 33 | } 34 | 35 | if ($scope->isInTrait()) { 36 | return []; 37 | } 38 | 39 | if ($node->name->name !== '__construct') { 40 | return []; 41 | } 42 | 43 | if ($node->isAbstract()) { 44 | return []; 45 | } 46 | 47 | $classReflection = $scope->getClassReflection()->getNativeReflection(); 48 | if ($classReflection->isInterface() || $classReflection->isAnonymous()) { 49 | return []; 50 | } 51 | 52 | if ($this->callsParentConstruct($node)) { 53 | return []; 54 | } 55 | 56 | $parentClass = $this->getParentConstructorClass($classReflection); 57 | if ($parentClass !== false) { 58 | return [ 59 | RuleErrorBuilder::message(sprintf( 60 | '%s::__construct() does not call parent constructor from %s.', 61 | $classReflection->getName(), 62 | $parentClass->getName(), 63 | ))->identifier('constructor.missingParentCall')->build(), 64 | ]; 65 | } 66 | 67 | return []; 68 | } 69 | 70 | private function callsParentConstruct(Node $parserNode): bool 71 | { 72 | if (!property_exists($parserNode, 'stmts')) { 73 | return false; 74 | } 75 | 76 | foreach ($parserNode->stmts as $statement) { 77 | if ($statement instanceof Node\Stmt\Expression) { 78 | $statement = $statement->expr; 79 | } 80 | 81 | $statement = $this->ignoreErrorSuppression($statement); 82 | if ($statement instanceof StaticCall) { 83 | if ( 84 | $statement->class instanceof Name 85 | && ((string) $statement->class === 'parent') 86 | && $statement->name instanceof Node\Identifier 87 | && $statement->name->name === '__construct' 88 | ) { 89 | return true; 90 | } 91 | } else { 92 | if ($this->callsParentConstruct($statement)) { 93 | return true; 94 | } 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | /** 102 | * @param ReflectionClass|ReflectionEnum $classReflection 103 | * @return ReflectionClass|false 104 | */ 105 | private function getParentConstructorClass($classReflection) 106 | { 107 | $parentClass = $classReflection->getParentClass(); 108 | while ($parentClass !== false) { 109 | $constructor = $parentClass->hasMethod('__construct') ? $parentClass->getMethod('__construct') : null; 110 | $constructorWithClassName = $parentClass->hasMethod($parentClass->getName()) ? $parentClass->getMethod($parentClass->getName()) : null; 111 | if ( 112 | ( 113 | $constructor !== null 114 | && $constructor->getDeclaringClass()->getName() === $parentClass->getName() 115 | && !$constructor->isAbstract() 116 | && !$constructor->isPrivate() 117 | && !$constructor->isDeprecated() 118 | ) || ( 119 | $constructorWithClassName !== null 120 | && $constructorWithClassName->getDeclaringClass()->getName() === $parentClass->getName() 121 | && !$constructorWithClassName->isAbstract() 122 | ) 123 | ) { 124 | return $parentClass; 125 | } 126 | 127 | $parentClass = $parentClass->getParentClass(); 128 | } 129 | 130 | return false; 131 | } 132 | 133 | private function ignoreErrorSuppression(Node $statement): Node 134 | { 135 | if ($statement instanceof Node\Expr\ErrorSuppress) { 136 | 137 | return $statement->expr; 138 | } 139 | 140 | return $statement; 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/Rules/Functions/ArrayFilterStrictRule.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class ArrayFilterStrictRule implements Rule 24 | { 25 | 26 | private ReflectionProvider $reflectionProvider; 27 | 28 | private bool $treatPhpDocTypesAsCertain; 29 | 30 | private bool $checkNullables; 31 | 32 | private bool $treatPhpDocTypesAsCertainTip; 33 | 34 | public function __construct( 35 | ReflectionProvider $reflectionProvider, 36 | bool $treatPhpDocTypesAsCertain, 37 | bool $checkNullables, 38 | bool $treatPhpDocTypesAsCertainTip 39 | ) 40 | { 41 | $this->reflectionProvider = $reflectionProvider; 42 | $this->treatPhpDocTypesAsCertain = $treatPhpDocTypesAsCertain; 43 | $this->checkNullables = $checkNullables; 44 | $this->treatPhpDocTypesAsCertainTip = $treatPhpDocTypesAsCertainTip; 45 | } 46 | 47 | public function getNodeType(): string 48 | { 49 | return FuncCall::class; 50 | } 51 | 52 | public function processNode(Node $node, Scope $scope): array 53 | { 54 | if (!$node->name instanceof Name) { 55 | return []; 56 | } 57 | 58 | if (!$this->reflectionProvider->hasFunction($node->name, $scope)) { 59 | return []; 60 | } 61 | 62 | $functionReflection = $this->reflectionProvider->getFunction($node->name, $scope); 63 | 64 | if ($functionReflection->getName() !== 'array_filter') { 65 | return []; 66 | } 67 | 68 | $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( 69 | $scope, 70 | $node->getArgs(), 71 | $functionReflection->getVariants(), 72 | $functionReflection->getNamedArgumentsVariants(), 73 | ); 74 | 75 | $normalizedFuncCall = ArgumentsNormalizer::reorderFuncArguments($parametersAcceptor, $node); 76 | 77 | if ($normalizedFuncCall === null) { 78 | return []; 79 | } 80 | 81 | $args = $normalizedFuncCall->getArgs(); 82 | if (count($args) === 0) { 83 | return []; 84 | } 85 | 86 | if (count($args) === 1) { 87 | $arrayType = $scope->getType($args[0]->value); 88 | $itemType = $arrayType->getIterableValueType(); 89 | if ($itemType instanceof UnionType) { 90 | $hasTruthy = false; 91 | $hasFalsey = false; 92 | foreach ($itemType->getTypes() as $innerType) { 93 | $booleanType = $innerType->toBoolean(); 94 | if ($booleanType->isTrue()->yes()) { 95 | $hasTruthy = true; 96 | continue; 97 | } 98 | if ($booleanType->isFalse()->yes()) { 99 | $hasFalsey = true; 100 | continue; 101 | } 102 | 103 | $hasTruthy = false; 104 | $hasFalsey = false; 105 | break; 106 | } 107 | 108 | if ($hasTruthy && $hasFalsey) { 109 | return []; 110 | } 111 | } elseif ($itemType->isBoolean()->yes()) { 112 | return []; 113 | } elseif ($itemType->isArray()->yes()) { 114 | return []; 115 | } 116 | 117 | return [ 118 | RuleErrorBuilder::message('Call to function array_filter() requires parameter #2 to be passed to avoid loose comparison semantics.') 119 | ->identifier('arrayFilter.strict') 120 | ->build(), 121 | ]; 122 | } 123 | 124 | $nativeCallbackType = $scope->getNativeType($args[1]->value); 125 | 126 | if ($this->treatPhpDocTypesAsCertain) { 127 | $callbackType = $scope->getType($args[1]->value); 128 | } else { 129 | $callbackType = $nativeCallbackType; 130 | } 131 | 132 | if ($this->isCallbackTypeNull($callbackType)) { 133 | $message = 'Parameter #2 of array_filter() cannot be null to avoid loose comparison semantics (%s given).'; 134 | $errorBuilder = RuleErrorBuilder::message(sprintf( 135 | $message, 136 | $callbackType->describe(VerbosityLevel::typeOnly()), 137 | ))->identifier('arrayFilter.strict'); 138 | 139 | if ($this->treatPhpDocTypesAsCertainTip && !$this->isCallbackTypeNull($nativeCallbackType) && $this->treatPhpDocTypesAsCertain) { 140 | $errorBuilder->treatPhpDocTypesAsCertainTip(); 141 | } 142 | 143 | return [$errorBuilder->build()]; 144 | } 145 | 146 | return []; 147 | } 148 | 149 | private function isCallbackTypeNull(Type $callbackType): bool 150 | { 151 | if ($callbackType->isNull()->yes()) { 152 | return true; 153 | } 154 | 155 | if ($callbackType->isNull()->no()) { 156 | return false; 157 | } 158 | 159 | return $this->checkNullables; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extra strict and opinionated rules for PHPStan 2 | 3 | [![Build](https://github.com/phpstan/phpstan-strict-rules/workflows/Build/badge.svg)](https://github.com/phpstan/phpstan-strict-rules/actions) 4 | [![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-strict-rules/v/stable)](https://packagist.org/packages/phpstan/phpstan-strict-rules) 5 | [![License](https://poser.pugx.org/phpstan/phpstan-strict-rules/license)](https://packagist.org/packages/phpstan/phpstan-strict-rules) 6 | 7 | [PHPStan](https://phpstan.org/) focuses on finding bugs in your code. But in PHP there's a lot of leeway in how stuff can be written. This repository contains additional rules that revolve around strictly and strongly typed code with no loose casting for those who want additional safety in extremely defensive programming: 8 | 9 | | Configuration Parameters | Rule Description | 10 | |:---------------------------------------|:--------------------------------------------------------------------------------------------------------| 11 | | `booleansInConditions` | Require booleans in `if`, `elseif`, ternary operator, after `!`, and on both sides of `&&` and `\|\|`. | 12 | | `booleansInLoopConditions` | Require booleans in `while` and `do while` loop conditions. | 13 | | `numericOperandsInArithmeticOperators` | Require numeric operand in `+$var`, `-$var`, `$var++`, `$var--`, `++$var` and `--$var`. | 14 | | `strictFunctionCalls` | These functions contain a `$strict` parameter for better type safety, it must be set to `true`:
* `in_array` (3rd parameter)
* `array_search` (3rd parameter)
* `array_keys` (3rd parameter; only if the 2nd parameter `$search_value` is provided)
* `base64_decode` (2nd parameter). | 15 | | `overwriteVariablesWithLoop` | * Disallow overwriting variables with `foreach` key and value variables.
* Disallow overwriting variables with `for` loop initial assignment. | 16 | | `switchConditionsMatchingType` | Types in `switch` condition and `case` value must match. PHP compares them loosely by default and that can lead to unexpected results. | 17 | | `dynamicCallOnStaticMethod` | Check that statically declared methods are called statically. | 18 | | `disallowedEmpty` | Disallow `empty()` - it's a very loose comparison (see [manual](https://php.net/empty)), it's recommended to use more strict one. | 19 | | `disallowedShortTernary` | Disallow short ternary operator (`?:`) - implies weak comparison, it's recommended to use null coalesce operator (`??`) or ternary operator with strict condition. | 20 | | `noVariableVariables` | Disallow variable variables (`$$foo`, `$this->$method()` etc.). | 21 | | `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall`, `checkAlwaysTrueStrictComparison` | Always true `instanceof`, type-checking `is_*` functions and strict comparisons `===`/`!==`. These checks can be turned off by setting `checkAlwaysTrueInstanceof`, `checkAlwaysTrueCheckTypeFunctionCall` and `checkAlwaysTrueStrictComparison` to false. | 22 | | | Correct case for referenced and called function names. | 23 | | `matchingInheritedMethodNames` | Correct case for inherited and implemented method names. | 24 | | | Contravariance for parameter types and covariance for return types in inherited methods (also known as Liskov substitution principle - LSP).| 25 | | | Check LSP even for static methods. | 26 | | `requireParentConstructorCall` | Require calling parent constructor. | 27 | | `disallowedBacktick` | Disallow usage of backtick operator (`` $ls = `ls -la` ``). | 28 | | `closureUsesThis` | Closure should use `$this` directly instead of using `$this` variable indirectly. | 29 | 30 | Additional rules are coming in subsequent releases! 31 | 32 | 33 | ## Installation 34 | 35 | To use this extension, require it in [Composer](https://getcomposer.org/): 36 | 37 | ``` 38 | composer require --dev phpstan/phpstan-strict-rules 39 | ``` 40 | 41 | If you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set! 42 | 43 |
44 | Manual installation 45 | 46 | If you don't want to use `phpstan/extension-installer`, include rules.neon in your project's PHPStan config: 47 | 48 | ``` 49 | includes: 50 | - vendor/phpstan/phpstan-strict-rules/rules.neon 51 | ``` 52 |
53 | 54 | ## Disabling rules 55 | 56 | You can disable rules using configuration parameters: 57 | 58 | ```neon 59 | parameters: 60 | strictRules: 61 | disallowedLooseComparison: false 62 | booleansInConditions: false 63 | booleansInLoopConditions: false 64 | uselessCast: false 65 | requireParentConstructorCall: false 66 | disallowedBacktick: false 67 | disallowedEmpty: false 68 | disallowedImplicitArrayCreation: false 69 | disallowedShortTernary: false 70 | overwriteVariablesWithLoop: false 71 | closureUsesThis: false 72 | matchingInheritedMethodNames: false 73 | numericOperandsInArithmeticOperators: false 74 | strictFunctionCalls: false 75 | dynamicCallOnStaticMethod: false 76 | switchConditionsMatchingType: false 77 | noVariableVariables: false 78 | strictArrayFilter: false 79 | illegalConstructorMethodCall: false 80 | ``` 81 | 82 | Aside from introducing new custom rules, phpstan-strict-rules also [change the default values of some configuration parameters](./rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). 83 | 84 | ## Enabling rules one-by-one 85 | 86 | If you don't want to start using all the available strict rules at once but only one or two, you can! 87 | 88 | You can disable all rules from the included `rules.neon` with: 89 | 90 | ```neon 91 | parameters: 92 | strictRules: 93 | allRules: false 94 | ``` 95 | 96 | Then you can re-enable individual rules with configuration parameters: 97 | 98 | ```neon 99 | parameters: 100 | strictRules: 101 | allRules: false 102 | booleansInConditions: true 103 | ``` 104 | 105 | Even with `strictRules.allRules` set to `false`, part of this package is still in effect. That's because phpstan-strict-rules also [change the default values of some configuration parameters](./rules.neon#L1) that are present in PHPStan itself. These parameters are [documented on phpstan.org](https://phpstan.org/config-reference#stricter-analysis). 106 | -------------------------------------------------------------------------------- /rules.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | strictRulesInstalled: true 3 | polluteScopeWithLoopInitialAssignments: false 4 | polluteScopeWithAlwaysIterableForeach: false 5 | polluteScopeWithBlock: false 6 | checkDynamicProperties: true 7 | checkExplicitMixedMissingReturn: true 8 | checkFunctionNameCase: true 9 | checkInternalClassCaseSensitivity: true 10 | reportMaybesInMethodSignatures: true 11 | reportStaticMethodSignatures: true 12 | reportMaybesInPropertyPhpDocTypes: true 13 | reportWrongPhpDocTypeInVarTag: true 14 | checkStrictPrintfPlaceholderTypes: true 15 | strictRules: 16 | allRules: true 17 | disallowedLooseComparison: %strictRules.allRules% 18 | booleansInConditions: %strictRules.allRules% 19 | booleansInLoopConditions: [%strictRules.allRules%, %featureToggles.bleedingEdge%] 20 | uselessCast: %strictRules.allRules% 21 | requireParentConstructorCall: %strictRules.allRules% 22 | disallowedBacktick: %strictRules.allRules% 23 | disallowedEmpty: %strictRules.allRules% 24 | disallowedImplicitArrayCreation: %strictRules.allRules% 25 | disallowedShortTernary: %strictRules.allRules% 26 | overwriteVariablesWithLoop: %strictRules.allRules% 27 | closureUsesThis: %strictRules.allRules% 28 | matchingInheritedMethodNames: %strictRules.allRules% 29 | numericOperandsInArithmeticOperators: %strictRules.allRules% 30 | strictFunctionCalls: %strictRules.allRules% 31 | dynamicCallOnStaticMethod: %strictRules.allRules% 32 | switchConditionsMatchingType: %strictRules.allRules% 33 | noVariableVariables: %strictRules.allRules% 34 | strictArrayFilter: %strictRules.allRules% 35 | illegalConstructorMethodCall: %strictRules.allRules% 36 | 37 | parametersSchema: 38 | strictRules: structure([ 39 | allRules: anyOf(bool(), arrayOf(bool())), 40 | disallowedLooseComparison: anyOf(bool(), arrayOf(bool())), 41 | booleansInConditions: anyOf(bool(), arrayOf(bool())) 42 | booleansInLoopConditions: anyOf(bool(), arrayOf(bool())) 43 | uselessCast: anyOf(bool(), arrayOf(bool())) 44 | requireParentConstructorCall: anyOf(bool(), arrayOf(bool())) 45 | disallowedBacktick: anyOf(bool(), arrayOf(bool())) 46 | disallowedEmpty: anyOf(bool(), arrayOf(bool())) 47 | disallowedImplicitArrayCreation: anyOf(bool(), arrayOf(bool())) 48 | disallowedShortTernary: anyOf(bool(), arrayOf(bool())) 49 | overwriteVariablesWithLoop: anyOf(bool(), arrayOf(bool())) 50 | closureUsesThis: anyOf(bool(), arrayOf(bool())) 51 | matchingInheritedMethodNames: anyOf(bool(), arrayOf(bool())) 52 | numericOperandsInArithmeticOperators: anyOf(bool(), arrayOf(bool())) 53 | strictFunctionCalls: anyOf(bool(), arrayOf(bool())) 54 | dynamicCallOnStaticMethod: anyOf(bool(), arrayOf(bool())) 55 | switchConditionsMatchingType: anyOf(bool(), arrayOf(bool())) 56 | noVariableVariables: anyOf(bool(), arrayOf(bool())) 57 | strictArrayFilter: anyOf(bool(), arrayOf(bool())) 58 | illegalConstructorMethodCall: anyOf(bool(), arrayOf(bool())) 59 | ]) 60 | 61 | conditionalTags: 62 | PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule: 63 | phpstan.rules.rule: %strictRules.disallowedLooseComparison% 64 | PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule: 65 | phpstan.rules.rule: %strictRules.booleansInConditions% 66 | PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule: 67 | phpstan.rules.rule: %strictRules.booleansInConditions% 68 | PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule: 69 | phpstan.rules.rule: %strictRules.booleansInConditions% 70 | PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule: 71 | phpstan.rules.rule: %strictRules.booleansInLoopConditions% 72 | PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule: 73 | phpstan.rules.rule: %strictRules.booleansInConditions% 74 | PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule: 75 | phpstan.rules.rule: %strictRules.booleansInConditions% 76 | PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule: 77 | phpstan.rules.rule: %strictRules.booleansInConditions% 78 | PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule: 79 | phpstan.rules.rule: %strictRules.booleansInLoopConditions% 80 | PHPStan\Rules\Cast\UselessCastRule: 81 | phpstan.rules.rule: %strictRules.uselessCast% 82 | PHPStan\Rules\Classes\RequireParentConstructCallRule: 83 | phpstan.rules.rule: %strictRules.requireParentConstructorCall% 84 | PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule: 85 | phpstan.rules.rule: %strictRules.disallowedBacktick% 86 | PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule: 87 | phpstan.rules.rule: %strictRules.disallowedEmpty% 88 | PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule: 89 | phpstan.rules.rule: %strictRules.disallowedImplicitArrayCreation% 90 | PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule: 91 | phpstan.rules.rule: %strictRules.disallowedShortTernary% 92 | PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule: 93 | phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% 94 | PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule: 95 | phpstan.rules.rule: %strictRules.overwriteVariablesWithLoop% 96 | PHPStan\Rules\Functions\ArrayFilterStrictRule: 97 | phpstan.rules.rule: %strictRules.strictArrayFilter% 98 | PHPStan\Rules\Functions\ClosureUsesThisRule: 99 | phpstan.rules.rule: %strictRules.closureUsesThis% 100 | PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule: 101 | phpstan.rules.rule: %strictRules.matchingInheritedMethodNames% 102 | PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule: 103 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 104 | PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule: 105 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 106 | PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule: 107 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 108 | PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule: 109 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 110 | PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule: 111 | phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%] 112 | PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule: 113 | phpstan.rules.rule: [%strictRules.numericOperandsInArithmeticOperators%, %featureToggles.bleedingEdge%] 114 | PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule: 115 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 116 | PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule: 117 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 118 | PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule: 119 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 120 | PHPStan\Rules\Operators\OperandsInArithmeticModuloRule: 121 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 122 | PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule: 123 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 124 | PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule: 125 | phpstan.rules.rule: %strictRules.numericOperandsInArithmeticOperators% 126 | PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule: 127 | phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% 128 | PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule: 129 | phpstan.rules.rule: %strictRules.dynamicCallOnStaticMethod% 130 | PHPStan\Rules\StrictCalls\StrictFunctionCallsRule: 131 | phpstan.rules.rule: %strictRules.strictFunctionCalls% 132 | PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule: 133 | phpstan.rules.rule: %strictRules.switchConditionsMatchingType% 134 | PHPStan\Rules\VariableVariables\VariableMethodCallRule: 135 | phpstan.rules.rule: %strictRules.noVariableVariables% 136 | PHPStan\Rules\VariableVariables\VariableMethodCallableRule: 137 | phpstan.rules.rule: %strictRules.noVariableVariables% 138 | PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule: 139 | phpstan.rules.rule: %strictRules.noVariableVariables% 140 | PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule: 141 | phpstan.rules.rule: %strictRules.noVariableVariables% 142 | PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule: 143 | phpstan.rules.rule: %strictRules.noVariableVariables% 144 | PHPStan\Rules\VariableVariables\VariableVariablesRule: 145 | phpstan.rules.rule: %strictRules.noVariableVariables% 146 | PHPStan\Rules\VariableVariables\VariablePropertyFetchRule: 147 | phpstan.rules.rule: %strictRules.noVariableVariables% 148 | PHPStan\Rules\Methods\IllegalConstructorMethodCallRule: 149 | phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% 150 | PHPStan\Rules\Methods\IllegalConstructorStaticCallRule: 151 | phpstan.rules.rule: %strictRules.illegalConstructorMethodCall% 152 | 153 | services: 154 | - 155 | class: PHPStan\Rules\BooleansInConditions\BooleanRuleHelper 156 | 157 | - 158 | class: PHPStan\Rules\Operators\OperatorRuleHelper 159 | 160 | - 161 | class: PHPStan\Rules\VariableVariables\VariablePropertyFetchRule 162 | arguments: 163 | universalObjectCratesClasses: %universalObjectCratesClasses% 164 | 165 | - 166 | class: PHPStan\Rules\DisallowedConstructs\DisallowedLooseComparisonRule 167 | 168 | - 169 | class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanAndRule 170 | 171 | - 172 | class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanNotRule 173 | 174 | - 175 | class: PHPStan\Rules\BooleansInConditions\BooleanInBooleanOrRule 176 | 177 | - 178 | class: PHPStan\Rules\BooleansInConditions\BooleanInDoWhileConditionRule 179 | 180 | - 181 | class: PHPStan\Rules\BooleansInConditions\BooleanInElseIfConditionRule 182 | 183 | - 184 | class: PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule 185 | 186 | - 187 | class: PHPStan\Rules\BooleansInConditions\BooleanInTernaryOperatorRule 188 | 189 | - 190 | class: PHPStan\Rules\BooleansInConditions\BooleanInWhileConditionRule 191 | 192 | - 193 | class: PHPStan\Rules\Cast\UselessCastRule 194 | arguments: 195 | treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% 196 | treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% 197 | 198 | - 199 | class: PHPStan\Rules\Classes\RequireParentConstructCallRule 200 | 201 | - 202 | class: PHPStan\Rules\DisallowedConstructs\DisallowedBacktickRule 203 | 204 | - 205 | class: PHPStan\Rules\DisallowedConstructs\DisallowedEmptyRule 206 | 207 | - 208 | class: PHPStan\Rules\DisallowedConstructs\DisallowedImplicitArrayCreationRule 209 | 210 | - 211 | class: PHPStan\Rules\DisallowedConstructs\DisallowedShortTernaryRule 212 | 213 | - 214 | class: PHPStan\Rules\ForeachLoop\OverwriteVariablesWithForeachRule 215 | 216 | - 217 | class: PHPStan\Rules\ForLoop\OverwriteVariablesWithForLoopInitRule 218 | 219 | - 220 | class: PHPStan\Rules\Functions\ArrayFilterStrictRule 221 | arguments: 222 | treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% 223 | checkNullables: %checkNullables% 224 | treatPhpDocTypesAsCertainTip: %tips.treatPhpDocTypesAsCertain% 225 | 226 | - 227 | class: PHPStan\Rules\Functions\ClosureUsesThisRule 228 | 229 | - 230 | class: PHPStan\Rules\Methods\WrongCaseOfInheritedMethodRule 231 | 232 | - 233 | class: PHPStan\Rules\Methods\IllegalConstructorMethodCallRule 234 | 235 | - 236 | class: PHPStan\Rules\Methods\IllegalConstructorStaticCallRule 237 | 238 | - 239 | class: PHPStan\Rules\Operators\OperandInArithmeticPostDecrementRule 240 | 241 | - 242 | class: PHPStan\Rules\Operators\OperandInArithmeticPostIncrementRule 243 | 244 | - 245 | class: PHPStan\Rules\Operators\OperandInArithmeticPreDecrementRule 246 | 247 | - 248 | class: PHPStan\Rules\Operators\OperandInArithmeticPreIncrementRule 249 | 250 | - 251 | class: PHPStan\Rules\Operators\OperandInArithmeticUnaryMinusRule 252 | 253 | - 254 | class: PHPStan\Rules\Operators\OperandInArithmeticUnaryPlusRule 255 | 256 | - 257 | class: PHPStan\Rules\Operators\OperandsInArithmeticAdditionRule 258 | 259 | - 260 | class: PHPStan\Rules\Operators\OperandsInArithmeticDivisionRule 261 | 262 | - 263 | class: PHPStan\Rules\Operators\OperandsInArithmeticExponentiationRule 264 | 265 | - 266 | class: PHPStan\Rules\Operators\OperandsInArithmeticModuloRule 267 | 268 | - 269 | class: PHPStan\Rules\Operators\OperandsInArithmeticMultiplicationRule 270 | 271 | - 272 | class: PHPStan\Rules\Operators\OperandsInArithmeticSubtractionRule 273 | 274 | - 275 | class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsRule 276 | 277 | - 278 | class: PHPStan\Rules\StrictCalls\DynamicCallOnStaticMethodsCallableRule 279 | 280 | - 281 | class: PHPStan\Rules\StrictCalls\StrictFunctionCallsRule 282 | 283 | - 284 | class: PHPStan\Rules\SwitchConditions\MatchingTypeInSwitchCaseConditionRule 285 | 286 | - 287 | class: PHPStan\Rules\VariableVariables\VariableMethodCallRule 288 | 289 | - 290 | class: PHPStan\Rules\VariableVariables\VariableMethodCallableRule 291 | 292 | - 293 | class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallRule 294 | 295 | - 296 | class: PHPStan\Rules\VariableVariables\VariableStaticMethodCallableRule 297 | 298 | - 299 | class: PHPStan\Rules\VariableVariables\VariableStaticPropertyFetchRule 300 | 301 | - 302 | class: PHPStan\Rules\VariableVariables\VariableVariablesRule 303 | --------------------------------------------------------------------------------