Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support deprecated magic __toString() in echo statement #37

Open
wants to merge 41 commits into
base: 1.1.x
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
ae33971
support deprecated magic __toString() in echo statement
staabm May 1, 2021
86ad5d0
cleanup
staabm May 1, 2021
0e7f1d1
check against null
staabm May 1, 2021
d5e3248
remove unnecessary $scope arg
staabm May 1, 2021
d555a07
wrap test into function() to prevent echoing the actual string
staabm May 1, 2021
8f530ff
Update EchoDeprecatedToStringRuleTest.php
staabm May 1, 2021
8f3cb87
fix cs
staabm May 1, 2021
514e904
fix cs
staabm May 1, 2021
2c04cf2
support echo with string concat
staabm May 1, 2021
024edd4
fix type error
staabm May 1, 2021
fed9f4f
Update tests/Rules/Deprecations/data/echo-deprecated-magic-method-tos…
staabm May 1, 2021
b410c0d
Update EchoDeprecatedToStringRuleTest.php
staabm May 1, 2021
4417914
more testcoverage
staabm May 1, 2021
f133d6b
cover __toString() without deprecation
staabm May 1, 2021
47c4a55
added more testcoverage
staabm May 1, 2021
f23f190
support nested expressions
staabm May 1, 2021
d720be6
fix cs
staabm May 1, 2021
2b38c72
fix cs
staabm May 1, 2021
2ab82ca
cover explicit (string) cast
staabm May 1, 2021
1b43bf1
impl string casts
staabm May 1, 2021
ea2038c
cover assignment in expression
staabm May 1, 2021
a111c5f
cover Node\Expr\AssignOp
staabm May 1, 2021
689ff9c
cover Expr\AssignOp\Coalesce
staabm May 1, 2021
ca8c02b
cover Expr\BinaryOp\Equal
staabm May 1, 2021
b6016a7
cover Expr\BinaryOp\Identical
staabm May 1, 2021
470ccd2
cover Expr\BinaryOp\NotEqual
staabm May 1, 2021
239be6a
cover Expr\BinaryOp\NotIdentical
staabm May 1, 2021
eed7f60
cover Expr\BinaryOp\Spaceship
staabm May 1, 2021
879df38
simplify
staabm May 1, 2021
266bf34
cover Node\Scalar\Encapsed
staabm May 1, 2021
4ae64bb
cover coalesce operator
staabm May 1, 2021
61c9b8f
fix cs
staabm May 1, 2021
c114463
extracted EchoDeprecatedBinaryOpToStringRule
staabm May 9, 2021
3adc283
register PHPStan\Rules\Deprecations\EchoDeprecatedBinaryOpToStringRul…
staabm May 9, 2021
a4138a7
take core-logic from CallToDeprecatedMethodRule
staabm May 9, 2021
c09043b
Update EchoDeprecatedBinaryOpToStringRule.php
staabm May 9, 2021
2ca2d51
fix cs
staabm May 9, 2021
830f6d5
removed copy/paste leftovers
staabm May 9, 2021
fef4fe7
fix test generic
staabm May 9, 2021
583279a
removed no longer needed RuleLevelHeper from tests
staabm May 9, 2021
867e2a5
fix analysis errors and cs
staabm May 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
services:
-
class: PHPStan\Rules\Deprecations\DeprecatedClassHelper
-
class: PHPStan\Rules\Deprecations\EchoDeprecatedBinaryOpToStringRule

rules:
- PHPStan\Rules\Deprecations\AccessDeprecatedPropertyRule
@@ -19,3 +21,7 @@ rules:
- PHPStan\Rules\Deprecations\TypeHintDeprecatedInFunctionSignatureRule
- PHPStan\Rules\Deprecations\UsageOfDeprecatedCastRule
- PHPStan\Rules\Deprecations\UsageOfDeprecatedTraitRule

conditionalTags:
PHPStan\Rules\Deprecations\EchoDeprecatedBinaryOpToStringRule:
phpstan.rules.rule: %featureToggles.bleedingEdge%
88 changes: 88 additions & 0 deletions src/Rules/Deprecations/EchoDeprecatedBinaryOpToStringRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Deprecations;

use PhpParser\Node;
use PhpParser\Node\Expr\BinaryOp;
use PHPStan\Analyser\Scope;
use PHPStan\Broker\Broker;
use PHPStan\Type\TypeUtils;

/**
* @implements \PHPStan\Rules\Rule<BinaryOp>
*/
class EchoDeprecatedBinaryOpToStringRule implements \PHPStan\Rules\Rule
{

/** @var Broker */
private $broker;

public function __construct(Broker $broker)
{
$this->broker = $broker;
}

public function getNodeType(): string
{
return BinaryOp::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (DeprecatedScopeHelper::isScopeDeprecated($scope)) {
return [];
}

$messages = [];
$message = $this->checkExpr($node->left, $scope);
if ($message) {
$messages[] = $message;
}
$message = $this->checkExpr($node->right, $scope);
if ($message) {
$messages[] = $message;
}

return $messages;
}

private function checkExpr(Node\Expr $node, Scope $scope): ?string
{
$methodCalledOnType = $scope->getType($node);
$referencedClasses = TypeUtils::getDirectClassNames($methodCalledOnType);

foreach ($referencedClasses as $referencedClass) {
try {
$classReflection = $this->broker->getClass($referencedClass);
$methodReflection = $classReflection->getNativeMethod('__toString');

if (!$methodReflection->isDeprecated()->yes()) {
return null;
}

$description = $methodReflection->getDeprecatedDescription();
if ($description === null) {
return sprintf(
'Call to deprecated method %s() of class %s.',
$methodReflection->getName(),
$methodReflection->getDeclaringClass()->getName()
);
}

return sprintf(
"Call to deprecated method %s() of class %s:\n%s",
$methodReflection->getName(),
$methodReflection->getDeclaringClass()->getName(),
$description
);
} catch (\PHPStan\Broker\ClassNotFoundException $e) {
// Other rules will notify if the class is not found
} catch (\PHPStan\Reflection\MissingMethodFromReflectionException $e) {
// Other rules will notify if the the method is not found
}
}

return null;
}

}
115 changes: 115 additions & 0 deletions src/Rules/Deprecations/EchoDeprecatedToStringRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Deprecations;

use PhpParser\Node;
use PhpParser\Node\Stmt\Echo_;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleLevelHelper;
use PHPStan\Type\ErrorType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

/**
* @implements \PHPStan\Rules\Rule<Echo_>
*/
class EchoDeprecatedToStringRule implements \PHPStan\Rules\Rule
{

/** @var RuleLevelHelper */
private $ruleLevelHelper;

public function __construct(RuleLevelHelper $ruleLevelHelper)
{
$this->ruleLevelHelper = $ruleLevelHelper;
}

public function getNodeType(): string
{
return Echo_::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (DeprecatedScopeHelper::isScopeDeprecated($scope)) {
return [];
}

$messages = [];
foreach ($node->exprs as $key => $expr) {
$this->deepCheckExpr($expr, $scope, $messages);
}

return $messages;
}

/**
* @param Node\Expr $expr
* @param Scope $scope
* @param string[] $messages
*/
private function deepCheckExpr(Node\Expr $expr, Scope $scope, array &$messages): void
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a followup step, I would extract this logic into a helper and add a separate rule for Print.
Additionally a Rule could be added for the string-concat case

{
if ($expr instanceof Node\Expr\BinaryOp) {
$this->deepCheckExpr($expr->left, $scope, $messages);
$this->deepCheckExpr($expr->right, $scope, $messages);
} elseif ($expr instanceof Node\Expr\Cast\String_ || $expr instanceof Node\Expr\Assign || $expr instanceof Node\Expr\AssignOp) {
$this->deepCheckExpr($expr->expr, $scope, $messages);
} elseif ($expr instanceof Node\Scalar\Encapsed) {
foreach ($expr->parts as $part) {
$this->deepCheckExpr($part, $scope, $messages);
}
} else {
$message = $this->checkExpr($expr, $scope);

if ($message) {
$messages[] = $message;
}
}
}

private function checkExpr(Node\Expr $expr, Scope $scope): ?string
{
$type = $this->ruleLevelHelper->findTypeToCheck(
$scope,
$expr,
'',
static function (Type $type): bool {
return !$type->toString() instanceof ErrorType;
}
)->getType();

if (!$type instanceof ObjectType) {
return null;
}

$classReflection = $type->getClassReflection();

if ($classReflection === null) {
return null;
}

$methodReflection = $classReflection->getNativeMethod('__toString');

if (!$methodReflection->isDeprecated()->yes()) {
return null;
}

$description = $methodReflection->getDeprecatedDescription();
if ($description === null) {
return sprintf(
'Call to deprecated method %s() of class %s.',
$methodReflection->getName(),
$methodReflection->getDeclaringClass()->getName()
);
}

return sprintf(
"Call to deprecated method %s() of class %s:\n%s",
$methodReflection->getName(),
$methodReflection->getDeclaringClass()->getName(),
$description
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Deprecations;

/**
* @extends \PHPStan\Testing\RuleTestCase<EchoDeprecatedBinaryOpToStringRule>
*/
class EchoDeprecatedBinaryOpToStringRuleTest extends \PHPStan\Testing\RuleTestCase
{

protected function getRule(): \PHPStan\Rules\Rule
{
return new EchoDeprecatedBinaryOpToStringRule($this->createBroker());
}

public function testDeprecatedMagicMethodToStringCall(): void
{
require_once __DIR__ . '/data/echo-deprecated-binaryop-magic-method-tostring.php';
$this->analyse(
[__DIR__ . '/data/echo-deprecated-binaryop-magic-method-tostring.php'],
[
[
'Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBar.',
8,
],
[
'Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBar.',
9,
],
[
'Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBar.',
10,
],
[
'Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBar.',
11,
],
[
'Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBar.',
12,
],
[
"Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBarWithDesc:\nuse XY instead.",
15,
],
[
"Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBarWithDesc:\nuse XY instead.",
16,
],
[
"Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBarWithDesc:\nuse XY instead.",
17,
],
[
"Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBarWithDesc:\nuse XY instead.",
18,
],
[
"Call to deprecated method __toString() of class EchoDeprecatedBinaryOpToStringRule\MagicBarWithDesc:\nuse XY instead.",
19,
],
]
);
}

}
Loading