Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/AssignOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantIntegerType;
Expand Down Expand Up @@ -99,6 +100,8 @@ static function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $contex
$toStringResult = $this->implicitToStringCallHelper->processImplicitToStringCall($expr->expr, $scope);
$throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());

$nodeScopeResolver->callNodeCallback($nodeCallback, new ExprUsedAsStringNode($expr->expr, $expr), $scope, $storage);
}

return new ExpressionResult(
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/CastStringHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Type\Type;
use function array_merge;
Expand Down Expand Up @@ -46,6 +47,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());

$nodeScopeResolver->callNodeCallback($nodeCallback, new ExprUsedAsStringNode($expr->expr, $expr), $scope, $storage);

$scope = $exprResult->getScope();

return new ExpressionResult(
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/InterpolatedStringHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -61,6 +62,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $partResult->getScope();
}

$nodeScopeResolver->callNodeCallback($nodeCallback, new ExprUsedAsStringNode($expr, $expr), $scope, $storage);

return new ExpressionResult(
$scope,
hasYield: $hasYield,
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/PrintHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Type;
use function array_merge;
Expand Down Expand Up @@ -51,6 +52,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());

$nodeScopeResolver->callNodeCallback($nodeCallback, new ExprUsedAsStringNode($expr->expr, $expr), $scope, $storage);

$scope = $exprResult->getScope();

return new ExpressionResult(
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
use PHPStan\Node\Expr\PropertyInitializationExpr;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Node\Expr\UnsetOffsetExpr;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Node\FinallyExitPointsNode;
use PHPStan\Node\FunctionCallableNode;
use PHPStan\Node\FunctionReturnStatementsNode;
Expand Down Expand Up @@ -996,6 +997,7 @@ public function processStmtNode(
$toStringResult = $this->implicitToStringCallHelper->processImplicitToStringCall($echoExpr, $scope);
$throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());
$this->callNodeCallback($nodeCallback, new ExprUsedAsStringNode($echoExpr, $stmt), $scope, $storage);
$scope = $result->getScope();
$hasYield = $hasYield || $result->hasYield();
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating();
Expand Down Expand Up @@ -2402,6 +2404,7 @@ public function leaveNode(Node $node): ?ExistingArrayDimFetch
$impurePoints = [
new ImpurePoint($scope, $stmt, 'betweenPhpTags', 'output between PHP opening and closing tags', true),
];
$this->callNodeCallback($nodeCallback, new ExprUsedAsStringNode(new TypeExpr(new ConstantStringType($stmt->value)), $stmt), $scope, $storage);
} elseif ($stmt instanceof Node\Stmt\Block) {
$result = $this->processStmtNodesInternal($stmt, $stmt->stmts, $scope, $storage, $nodeCallback, $context);
if ($this->polluteScopeWithBlock) {
Expand Down
46 changes: 46 additions & 0 deletions src/Node/ExprUsedAsStringNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php declare(strict_types = 1);

namespace PHPStan\Node;

use Override;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\NodeAbstract;

/**
* @api
*/
final class ExprUsedAsStringNode extends NodeAbstract implements VirtualNode
{

public function __construct(private Expr $expression, private Expr|Node\Stmt $originalNode)
{
parent::__construct($originalNode->getAttributes());
}

public function getExpression(): Expr
{
return $this->expression;
}

public function getOriginalNode(): Expr|Node\Stmt
{
return $this->originalNode;
}

#[Override]
public function getType(): string
{
return 'PHPStan_Node_ExprUsedAsStringNode';
}

/**
* @return string[]
*/
#[Override]
public function getSubNodeNames(): array
{
return [];
}

}
71 changes: 71 additions & 0 deletions tests/PHPStan/Rules/ExprUsedAsString/ExprUsedAsStringRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\ExprUsedAsString;

use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<ExprUsedAsStringTestRule>
*/
class ExprUsedAsStringRuleTest extends RuleTestCase
{

protected function getRule(): Rule
{
return new ExprUsedAsStringTestRule();
}

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/expr-used-as-string.php'], [
[
'Expression used as string: PhpParser\Node\Scalar\String_ in PhpParser\Node\Stmt\Echo_',
8,
],
[
'Expression used as string: PhpParser\Node\Scalar\String_ in PhpParser\Node\Stmt\Echo_',
9,
],
[
'Expression used as string: PhpParser\Node\Scalar\String_ in PhpParser\Node\Stmt\Echo_',
9,
],
[
'Expression used as string: PhpParser\Node\Scalar\String_ in PhpParser\Node\Expr\Print_',
13,
],
[
'Expression used as string: PhpParser\Node\Expr\Variable in PhpParser\Node\Expr\Cast\String_',
17,
],
[
'Expression used as string: PhpParser\Node\Scalar\InterpolatedString in PhpParser\Node\Scalar\InterpolatedString',
26,
],
[
'Expression used as string: PhpParser\Node\Expr\Variable in PhpParser\Node\Expr\AssignOp\Concat',
30,
],
[
'Expression used as string: PhpParser\Node\Expr\BinaryOp\Concat in PhpParser\Node\Stmt\Echo_',
36,
],
[
'Expression used as string: PhpParser\Node\Scalar\InterpolatedString in PhpParser\Node\Scalar\InterpolatedString',
41,
],
]);
}

public function testInlineHtml(): void
{
$this->analyse([__DIR__ . '/data/inline-html.php'], [
[
'Expression used as string: PHPStan\Node\Expr\TypeExpr in PhpParser\Node\Stmt\InlineHTML',
7,
],
]);
}

}
35 changes: 35 additions & 0 deletions tests/PHPStan/Rules/ExprUsedAsString/ExprUsedAsStringTestRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\ExprUsedAsString;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\ExprUsedAsStringNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function get_class;

/**
* @implements Rule<ExprUsedAsStringNode>
*/
class ExprUsedAsStringTestRule implements Rule
{

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

public function processNode(Node $node, Scope $scope): array
{
$expr = $node->getExpression();
$originalNode = $node->getOriginalNode();

return [
RuleErrorBuilder::message('Expression used as string: ' . get_class($expr) . ' in ' . get_class($originalNode))
->identifier('tests.exprUsedAsString')
->build(),
];
}

}
44 changes: 44 additions & 0 deletions tests/PHPStan/Rules/ExprUsedAsString/data/expr-used-as-string.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php // lint >= 8.0

namespace ExprUsedAsString;

class Foo {

public function doEcho(): void {
echo 'hello';
echo 'hello', ' world';
}

public function doPrint(): void {
print 'hello';
}

public function doCast(int $i): void {
(string) $i;
}

public function doConcat(string $a, string $b): void {
$a . $b;
'a' . $b . 'c';
}

public function doInterpolatedString(string $name): void {
"Hello $name!";
}

public function doConcatAssign(string $a, string $b): void {
$a .= $b;
}

}

function doEchoConcat(string $s): void {
echo '<script src="' . $s . '" nonce=123></script>';
}

function doHeredoc(): void {
$nonce = '123';
$html = <<<EOS
<script nonce="{$nonce}" type="module">
EOS;
}
9 changes: 9 additions & 0 deletions tests/PHPStan/Rules/ExprUsedAsString/data/inline-html.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace ExprUsedAsStringInlineHtml;

function doFoo(): void {
?>
<script src="my.js" nonce=123></script>
<?php
}
Loading