diff --git a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php index 05cdafe663..f28b0b182c 100644 --- a/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php +++ b/src/Rules/Methods/CallPrivateMethodThroughStaticRule.php @@ -57,7 +57,10 @@ public function processNode(Node $node, Scope $scope): array 'Unsafe call to private method %s::%s() through static::.', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier('staticClassAccess.privateMethod')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticClassAccess.privateMethod') + ->build(), ]; } diff --git a/src/Rules/Methods/CallStaticMethodsRule.php b/src/Rules/Methods/CallStaticMethodsRule.php index 55537f84f0..166ad74ace 100644 --- a/src/Rules/Methods/CallStaticMethodsRule.php +++ b/src/Rules/Methods/CallStaticMethodsRule.php @@ -65,7 +65,7 @@ public function processNode(Node $node, Scope $scope): array */ private function processSingleMethodCall(Scope $scope, StaticCall $node, string $methodName): array { - [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class); + [$errors, $method] = $this->methodCallCheck->check($scope, $methodName, $node->class, $node->name); if ($method === null) { return $errors; } diff --git a/src/Rules/Methods/MethodCallCheck.php b/src/Rules/Methods/MethodCallCheck.php index 3be2032165..0a5c11b454 100644 --- a/src/Rules/Methods/MethodCallCheck.php +++ b/src/Rules/Methods/MethodCallCheck.php @@ -72,7 +72,10 @@ public function check( 'Cannot call method %s() on %s.', $methodName, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('method.nonObject')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.nonObject') + ->build(), ], null, ]; @@ -106,7 +109,10 @@ public function check( 'Call to private method %s() of parent class %s.', $methodReflection->getName(), $parentClassReflection->getDisplayName(), - ))->identifier('method.private')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.private') + ->build(), ], $methodReflection, ]; @@ -133,7 +139,10 @@ public function check( 'Call to an undefined method %s::%s().', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $methodName, - ))->identifier('method.notFound')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.notFound') + ->build(), ], null, ]; @@ -150,6 +159,7 @@ public function check( $methodReflection->getName(), $declaringClass->getDisplayName(), )) + ->line($astName->getStartLine()) ->identifier(sprintf('method.%s', $methodReflection->isPrivate() ? 'private' : 'protected')) ->build(); } @@ -161,7 +171,10 @@ public function check( ) { $errors[] = RuleErrorBuilder::message( sprintf('Call to method %s with incorrect case: %s', $messagesMethodName, $methodName), - )->identifier('method.nameCase')->build(); + ) + ->line($astName->getStartLine()) + ->identifier('method.nameCase') + ->build(); } return [$errors, $methodReflection]; diff --git a/src/Rules/Methods/NullsafeMethodCallRule.php b/src/Rules/Methods/NullsafeMethodCallRule.php index a6750d58a3..12ac117ce0 100644 --- a/src/Rules/Methods/NullsafeMethodCallRule.php +++ b/src/Rules/Methods/NullsafeMethodCallRule.php @@ -31,6 +31,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf('Using nullsafe method call on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly()))) + ->line($node->name->getStartLine()) ->identifier('nullsafe.neverNull') ->build(), ]; diff --git a/src/Rules/Methods/StaticMethodCallCheck.php b/src/Rules/Methods/StaticMethodCallCheck.php index b0a6f057e5..aaabe3d1e7 100644 --- a/src/Rules/Methods/StaticMethodCallCheck.php +++ b/src/Rules/Methods/StaticMethodCallCheck.php @@ -4,6 +4,7 @@ use DOMDocument; use PhpParser\Node\Expr; +use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; @@ -61,6 +62,7 @@ public function check( Scope $scope, string $methodName, $class, + Identifier|Expr $astName, ): array { $errors = []; @@ -81,7 +83,9 @@ public function check( 'Calling %s::%s() outside of class scope.', $className, $methodName, - ))->identifier(sprintf('outOfClass.%s', $lowercasedClassName))->build(), + )) + ->line($astName->getStartLine()) + ->identifier(sprintf('outOfClass.%s', $lowercasedClassName))->build(), ], null, ]; @@ -95,7 +99,10 @@ public function check( 'Calling %s::%s() outside of class scope.', $className, $methodName, - ))->identifier(sprintf('outOfClass.parent'))->build(), + )) + ->line($astName->getStartLine()) + ->identifier(sprintf('outOfClass.parent')) + ->build(), ], null, ]; @@ -110,7 +117,10 @@ public function check( $scope->getFunctionName(), $methodName, $scope->getClassReflection()->getDisplayName(), - ))->identifier('class.noParent')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('class.noParent') + ->build(), ], null, ]; @@ -132,6 +142,7 @@ public function check( $methodName, $className, )) + ->line($astName->getStartLine()) ->identifier('class.notFound'); if ($this->discoveringSymbolsTip) { @@ -206,7 +217,10 @@ public function check( 'Cannot call static method %s() on %s.', $methodName, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('staticMethod.nonObject')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.nonObject') + ->build(), ]), null, ]; @@ -232,7 +246,10 @@ public function check( 'Call to an undefined static method %s::%s().', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $methodName, - ))->identifier('staticMethod.notFound')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.notFound') + ->build(), ]), null, ]; @@ -265,7 +282,10 @@ public function check( 'Static call to instance method %s::%s().', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier('method.staticCall')->build(), + )) + ->line($astName->getStartLine()) + ->identifier('method.staticCall') + ->build(), ]), $method, ]; @@ -281,6 +301,7 @@ public function check( $method->getName(), $method->getDeclaringClass()->getDisplayName(), )) + ->line($astName->getStartLine()) ->identifier(sprintf('staticMethod.%s', $method->isPrivate() ? 'private' : 'protected')) ->build(), ]); @@ -294,10 +315,12 @@ public function check( $method->isStatic() ? ' static' : '', $method->getDeclaringClass()->getDisplayName(), $method->getName(), - ))->identifier(sprintf( - '%s.callToAbstract', - $method->isStatic() ? 'staticMethod' : 'method', - ))->build(), + )) + ->line($astName->getStartLine()) + ->identifier(sprintf( + '%s.callToAbstract', + $method->isStatic() ? 'staticMethod' : 'method', + ))->build(), ], $method, ]; @@ -317,7 +340,10 @@ public function check( 'Call to %s with incorrect case: %s', $lowercasedMethodName, $methodName, - ))->identifier('staticMethod.nameCase')->build(); + )) + ->line($astName->getStartLine()) + ->identifier('staticMethod.nameCase') + ->build(); } return [$errors, $method]; diff --git a/src/Rules/Methods/StaticMethodCallableRule.php b/src/Rules/Methods/StaticMethodCallableRule.php index e1b33d1321..5f10168c19 100644 --- a/src/Rules/Methods/StaticMethodCallableRule.php +++ b/src/Rules/Methods/StaticMethodCallableRule.php @@ -46,7 +46,7 @@ public function processNode(Node $node, Scope $scope): array $methodNameName = $methodName->toString(); - [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass()); + [$errors, $methodReflection] = $this->methodCallCheck->check($scope, $methodNameName, $node->getClass(), $node->getName()); if ($methodReflection === null) { return $errors; } diff --git a/src/Rules/Operators/PipeOperatorRule.php b/src/Rules/Operators/PipeOperatorRule.php index 07cfedeb7e..908141b8fd 100644 --- a/src/Rules/Operators/PipeOperatorRule.php +++ b/src/Rules/Operators/PipeOperatorRule.php @@ -54,7 +54,9 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message(sprintf( 'Parameter #1%s of callable on the right side of pipe operator is passed by reference.', $parameter->getName() !== '' ? ' $' . $parameter->getName() : '', - ))->identifier('pipe.byRef') + )) + ->line($node->right->getStartLine()) + ->identifier('pipe.byRef') ->build(), ]; } diff --git a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php index 44773b9275..6a0b1318b6 100644 --- a/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php +++ b/src/Rules/Properties/AccessPrivatePropertyThroughStaticRule.php @@ -56,7 +56,10 @@ public function processNode(Node $node, Scope $scope): array 'Unsafe access to private property %s::$%s through static::.', $property->getDeclaringClass()->getDisplayName(), $propertyName, - ))->identifier('staticClassAccess.privateProperty')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticClassAccess.privateProperty') + ->build(), ]; } diff --git a/src/Rules/Properties/AccessPropertiesCheck.php b/src/Rules/Properties/AccessPropertiesCheck.php index e9af849df6..b1e0514e8e 100644 --- a/src/Rules/Properties/AccessPropertiesCheck.php +++ b/src/Rules/Properties/AccessPropertiesCheck.php @@ -78,6 +78,7 @@ public function check(PropertyFetch $node, Scope $scope, bool $write): array $originalNameType = $scope->getType($node->name); $className = $scope->getType($node->var)->describe(VerbosityLevel::typeOnly()); $errors[] = RuleErrorBuilder::message(sprintf('Property name for %s must be a string, but %s was given.', $className, $originalNameType->describe(VerbosityLevel::precise()))) + ->line($node->name->getStartLine()) ->identifier('property.nameNotString') ->build(); } @@ -124,7 +125,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Cannot access property $%s on %s.', $name, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('property.nonObject')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.nonObject') + ->build(), ]; } @@ -183,7 +187,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Access to private property $%s of parent class %s.', $name, $parentClassReflection->getDisplayName(), - ))->identifier('property.private')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.private') + ->build(), ]; } @@ -208,7 +215,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string 'Non-static access to static property %s::$%s.', $type->getStaticProperty($name, $scope)->getDeclaringClass()->getDisplayName(), $name, - ))->identifier('staticProperty.nonStaticAccess')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.nonStaticAccess') + ->build(), ]; } @@ -255,7 +265,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $propertyReflection->isPrivate() ? 'private' : 'protected', $type->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('property.%s', $propertyReflection->isPrivate() ? 'private' : 'protected')) + ->build(), ]; } @@ -265,7 +278,10 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string $propertyReflection->isPrivateSet() ? 'private(set)' : 'protected(set)', $type->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier(sprintf('assign.property%s', $propertyReflection->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('assign.property%s', $propertyReflection->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet')) + ->build(), ]; } diff --git a/src/Rules/Properties/AccessStaticPropertiesCheck.php b/src/Rules/Properties/AccessStaticPropertiesCheck.php index 487453f428..7ad441ffed 100644 --- a/src/Rules/Properties/AccessStaticPropertiesCheck.php +++ b/src/Rules/Properties/AccessStaticPropertiesCheck.php @@ -83,7 +83,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Accessing %s::$%s outside of class scope.', $class, $name, - ))->identifier(sprintf('outOfClass.%s', $lowercasedClass))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('outOfClass.%s', $lowercasedClass)) + ->build(), ]; } $classType = $scope->resolveTypeByName($node->class); @@ -94,7 +97,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Accessing %s::$%s outside of class scope.', $class, $name, - ))->identifier('outOfClass.parent')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('outOfClass.parent') + ->build(), ]; } if ($scope->getClassReflection()->getParentClass() === null) { @@ -105,7 +111,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $scope->getFunctionName(), $name, $scope->getClassReflection()->getDisplayName(), - ))->identifier('class.noParent')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('class.noParent') + ->build(), ]; } @@ -121,6 +130,7 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $name, $class, )) + ->line($node->name->getStartLine()) ->identifier('class.notFound'); if ($this->discoveringSymbolsTip) { @@ -179,7 +189,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Cannot access static property $%s on %s.', $name, $typeForDescribe->describe(VerbosityLevel::typeOnly()), - ))->identifier('staticProperty.nonObject')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.nonObject') + ->build(), ]); } @@ -212,7 +225,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Access to private static property $%s of parent class %s.', $name, $parentClassReflection->getDisplayName(), - ))->identifier('staticProperty.private')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.private') + ->build(), ]; } @@ -233,7 +249,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Static access to instance property %s::$%s.', $classType->getInstanceProperty($name, $scope)->getDeclaringClass()->getDisplayName(), $name, - ))->identifier('property.staticAccess')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('property.staticAccess') + ->build(), ]); } @@ -242,7 +261,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, 'Access to an undefined static property %s::$%s.', $typeForDescribe->describe(VerbosityLevel::typeOnly()), $name, - ))->identifier('staticProperty.notFound')->build(), + )) + ->line($node->name->getStartLine()) + ->identifier('staticProperty.notFound') + ->build(), ]); } @@ -266,7 +288,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $property->isPrivate() ? 'private' : 'protected', $name, $property->getDeclaringClass()->getDisplayName(), - ))->identifier(sprintf('staticProperty.%s', $property->isPrivate() ? 'private' : 'protected'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('staticProperty.%s', $property->isPrivate() ? 'private' : 'protected')) + ->build(), ]); } @@ -276,7 +301,10 @@ private function processSingleProperty(Scope $scope, StaticPropertyFetch $node, $property->isPrivateSet() ? 'private(set)' : 'protected(set)', $name, $property->getDeclaringClass()->getDisplayName(), - ))->identifier(sprintf('assign.staticProperty%s', $property->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet'))->build(), + )) + ->line($node->name->getStartLine()) + ->identifier(sprintf('assign.staticProperty%s', $property->isPrivateSet() ? 'PrivateSet' : 'ProtectedSet')) + ->build(), ]); } diff --git a/src/Rules/Properties/NullsafePropertyFetchRule.php b/src/Rules/Properties/NullsafePropertyFetchRule.php index c8718cddde..c1d2233736 100644 --- a/src/Rules/Properties/NullsafePropertyFetchRule.php +++ b/src/Rules/Properties/NullsafePropertyFetchRule.php @@ -39,6 +39,7 @@ public function processNode(Node $node, Scope $scope): array return [ RuleErrorBuilder::message(sprintf('Using nullsafe property access on non-nullable type %s. Use -> instead.', $calledOnType->describe(VerbosityLevel::typeOnly()))) + ->line($node->name->getStartLine()) ->identifier('nullsafe.neverNull') ->build(), ]; diff --git a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php index 7c6f3d7159..e61a9a2426 100644 --- a/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyByPhpDocPropertyAssignRule.php @@ -82,6 +82,7 @@ public function processNode(Node $node, Scope $scope): array if (!$scope->isInClass()) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignOutOfClass') ->build(); continue; @@ -90,6 +91,7 @@ public function processNode(Node $node, Scope $scope): array $scopeClassReflection = $scope->getClassReflection(); if ($scopeClassReflection->getName() !== $declaringClass->getName()) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignOutOfClass') ->build(); continue; @@ -106,6 +108,7 @@ public function processNode(Node $node, Scope $scope): array ) { if (TypeUtils::findThisType($scope->getType($propertyFetch->var)) === null) { $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is not assigned on $this.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignNotOnThis') ->build(); } @@ -126,6 +129,7 @@ public function processNode(Node $node, Scope $scope): array } $errors[] = RuleErrorBuilder::message(sprintf('@readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyByPhpDocAssignNotInConstructor') ->build(); } diff --git a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php index 796f900868..987e000256 100644 --- a/src/Rules/Properties/ReadOnlyPropertyAssignRule.php +++ b/src/Rules/Properties/ReadOnlyPropertyAssignRule.php @@ -69,6 +69,7 @@ public function processNode(Node $node, Scope $scope): array if (!$scope->isInClass()) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignOutOfClass') ->build(); continue; @@ -77,6 +78,7 @@ public function processNode(Node $node, Scope $scope): array $scopeClassReflection = $scope->getClassReflection(); if ($scopeClassReflection->getName() !== $declaringClass->getName()) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of its declaring class.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignOutOfClass') ->build(); continue; @@ -93,6 +95,7 @@ public function processNode(Node $node, Scope $scope): array ) { if (TypeUtils::findThisType($scope->getType($propertyFetch->var)) === null) { $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is not assigned on $this.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignNotOnThis') ->build(); } @@ -109,6 +112,7 @@ public function processNode(Node $node, Scope $scope): array } $errors[] = RuleErrorBuilder::message(sprintf('Readonly property %s::$%s is assigned outside of the constructor.', $declaringClass->getDisplayName(), $propertyReflection->getName())) + ->line($propertyFetch->name->getStartLine()) ->identifier('property.readOnlyAssignNotInConstructor') ->build(); } diff --git a/src/Rules/Properties/TypesAssignedToPropertiesRule.php b/src/Rules/Properties/TypesAssignedToPropertiesRule.php index 472ce3568e..6e464bdd1d 100644 --- a/src/Rules/Properties/TypesAssignedToPropertiesRule.php +++ b/src/Rules/Properties/TypesAssignedToPropertiesRule.php @@ -99,6 +99,7 @@ private function processSingleProperty( $propertyType->describe($verbosityLevel), $assignedValueType->describe($verbosityLevel), )) + ->line($assignedExpr->getStartLine()) ->identifier('assign.propertyType') ->acceptsReasonsTip($accepts->reasons) ->build(), diff --git a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php index 8ac259be31..00617f1398 100644 --- a/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php +++ b/src/Rules/Properties/WritingToReadOnlyPropertiesRule.php @@ -61,7 +61,10 @@ public function processNode(Node $node, Scope $scope): array RuleErrorBuilder::message(sprintf( '%s is not writable.', $propertyDescription, - ))->identifier('assign.propertyReadOnly')->build(), + )) + ->line($propertyFetch->name->getStartLine()) + ->identifier('assign.propertyReadOnly') + ->build(), ]; } diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 1c56de9b57..ac8e1ad170 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3844,6 +3844,19 @@ public function testBug12875(): void $this->analyse([__DIR__ . '/data/bug-12875.php'], []); } + public function testBug14150(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-14150.php'], [ + [ + 'Call to an undefined method Bug14150Method\HelloWorld::y().', + 21, + ], + ]); + } + #[RequiresPhp('>= 8.1')] public function testBug13805(): void { diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 9c28da3416..3ecba00c33 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -909,6 +909,19 @@ public function testRestrictedInternalClassNameUsage(): void ]); } + public function testBug14150(): void + { + $this->checkThisOnly = false; + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-14150-static.php'], [ + [ + 'Call to an undefined static method Bug14150MethodStatic\HelloWorld::y().', + 21, + ], + ]); + } + public function testBug13556(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php index 80696dfe6b..a83d5429b8 100644 --- a/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php +++ b/tests/PHPStan/Rules/Methods/NullsafeMethodCallRuleTest.php @@ -37,6 +37,16 @@ public function testBug8664(): void $this->analyse([__DIR__ . '/../../Analyser/data/bug-8664.php'], []); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [ + [ + 'Using nullsafe method call on non-nullable type $this(Bug14150NullsafeMethod\HelloWorld). Use -> instead.', + 21, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testBug9293(): void { diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php new file mode 100644 index 0000000000..77b55ee4b4 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14150-nullsafe.php @@ -0,0 +1,23 @@ += 8.0 + +namespace Bug14150NullsafeMethod; + +class HelloWorld +{ + public int $x = 5; + + /** + * @return $this + */ + public function x() + { + return $this; + } + + public function testUnknownMethod(): void + { + $this + ->x() + ?->y(); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-14150-static.php b/tests/PHPStan/Rules/Methods/data/bug-14150-static.php new file mode 100644 index 0000000000..765252fb4a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-14150-static.php @@ -0,0 +1,23 @@ +x() + ->y(); + } +} diff --git a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php index cf01a546f1..a1873163d6 100644 --- a/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php +++ b/tests/PHPStan/Rules/Operators/PipeOperatorRuleTest.php @@ -35,4 +35,15 @@ public function testRule(): void ]); } + #[RequiresPhp('>= 8.5')] + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-pipe.php'], [ + [ + 'Parameter #1 $s of callable on the right side of pipe operator is passed by reference.', + 17, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-14150-pipe.php b/tests/PHPStan/Rules/Operators/data/bug-14150-pipe.php new file mode 100644 index 0000000000..66c16a8da7 --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-14150-pipe.php @@ -0,0 +1,25 @@ += 8.5 + +namespace Bug14150PipeOperator; + +class Foo +{ + + /** + * @param callable(string): string $cb + */ + public function doFoo(callable $cb): void + { + $a = 'hello'; + $a |> $cb + |> $cb + |> $cb + |> self::doBar(...); + } + + public static function doBar(string &$s): void + { + + } + +} diff --git a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php index 259f973c22..98d40e2e23 100644 --- a/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php +++ b/tests/PHPStan/Rules/Properties/NullsafePropertyFetchRuleTest.php @@ -55,6 +55,20 @@ public function testBug8517(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-8517.php'], []); } + public function testBug14150(): void + { + $this->analyse([__DIR__ . '/data/bug-14150-nullsafe.php'], [ + [ + 'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.', + 20, + ], + [ + 'Using nullsafe property access on non-nullable type $this(Bug14150NullsafeProperty\HelloWorld). Use -> instead.', + 27, + ], + ]); + } + #[RequiresPhp('>= 8.0')] public function testBug9105(): void { diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 29c2c32aca..5f3578f946 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -986,6 +986,22 @@ public function testBug13654(): void $this->analyse([__DIR__ . '/data/bug-13654.php'], []); } + public function testBug14150(): void + { + $this->checkExplicitMixed = true; + $this->checkImplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-14150.php'], [ + [ + 'Property Bug14150Properties\HelloWorld::$x (int) does not accept null.', + 20, + ], + [ + 'Property Bug14150Properties\HelloWorld::$x (int) does not accept null.', + 27, + ], + ]); + } + #[RequiresPhp('>= 8.5')] public function testCloneWith(): void { diff --git a/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php b/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php new file mode 100644 index 0000000000..0b78120183 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14150-nullsafe.php @@ -0,0 +1,29 @@ += 8.0 + +namespace Bug14150NullsafeProperty; + +class HelloWorld +{ + public int $x = 5; + + /** + * @return $this + */ + public function x() + { + return $this; + } + + public function test3(): void + { + $this + ?->x; + } + + public function test4(): void + { + $this + ->x() + ?->x; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-14150.php b/tests/PHPStan/Rules/Properties/data/bug-14150.php new file mode 100644 index 0000000000..43fd8ce37c --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-14150.php @@ -0,0 +1,29 @@ +x = null; + } + + public function test4(): void + { + $this + ->x() + ->x = null; + } +}