Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PHPStan\Testing;

use LogicException;
use PhpParser\Node;
use PHPStan\Analyser\Analyser;
use PHPStan\Analyser\Error;
Expand Down Expand Up @@ -29,8 +30,12 @@
use PHPStan\Type\FileTypeMapper;
use function array_map;
use function count;
use function explode;
use function file_get_contents;
use function implode;
use function preg_match_all;
use function sprintf;
use function trim;

/**
* @api
Expand Down Expand Up @@ -158,6 +163,53 @@ static function (Error $error) use ($strictlyTypedSprintf): string {
$this->assertSame(implode("\n", $expectedErrors) . "\n", implode("\n", $actualErrors) . "\n");
}

/**
* Any single line comment prefixed 'error:' is used as expected error
*/
protected function analyseFileWithErrorsAsComments(string $file): void
{
$file = self::getFileHelper()->normalizePath($file);
$this->analyse([$file], $this->parseExpectedErrorsFromComments($file));
}

/**
* @return list<array{0: string, 1: int}>
*/
private function parseExpectedErrorsFromComments(string $file): array
{
$fileData = file_get_contents($file);

if ($fileData === false) {
throw new LogicException('Error while reading data from ' . $file);
}

$fileDataLines = explode("\n", $fileData);

$expectedErrors = [];

foreach ($fileDataLines as $line => $row) {
$matches = [];
$matched = preg_match_all('#// error:(.+)#', $row, $matches);

if ($matched === false) {
throw new LogicException('Error while matching errors');
}

if ($matched === 0) {
continue;
}

foreach ($matches[1] as $error) {
$expectedErrors[] = [
trim($error),
$line + 1,
];
}
}

return $expectedErrors;
}

/**
* @param string[] $files
* @return list<Error>
Expand Down
27 changes: 1 addition & 26 deletions tests/PHPStan/Rules/Cast/InvalidCastRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,7 @@ protected function getRule(): Rule

public function testRule(): void
{
$this->analyse([__DIR__ . '/data/invalid-cast.php'], [
[
'Cannot cast stdClass to string.',
7,
],
[
'Cannot cast stdClass to int.',
23,
],
[
'Cannot cast stdClass to float.',
24,
],
[
'Cannot cast object to string.',
35,
],
[
'Cannot cast Test\\Foo to string.',
41,
],
[
'Cannot cast array|float|int to string.',
48,
],
]);
$this->analyseFileWithErrorsAsComments(__DIR__ . '/data/invalid-cast.php');
}

public function testBug5162(): void
Expand Down
12 changes: 6 additions & 6 deletions tests/PHPStan/Rules/Cast/data/invalid-cast.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ function (
string $str
) {
(string) $str;
(string) new \stdClass();
(string) new \stdClass(); // error: Cannot cast stdClass to string.
Copy link
Contributor

@staabm staabm Nov 19, 2023

Choose a reason for hiding this comment

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

what comes to mind: maybe we can do it similar to assertType() and assertVariableCertainty()

Suggested change
(string) new \stdClass(); // error: Cannot cast stdClass to string.
assertErrorMessage('Cannot cast stdClass to string.', (string) new \stdClass());

?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I'd like something like that too. Perhaps something like assertErrorOnNextLine('message');

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is much less flexible. You cannot call method between class methods etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Error can be anywhere, in phpdoc, at class level, at trait usage. Comment is the only thing you can place anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, the implemented version works even within phpdocs (to be supported in #2807)

(string) new \Test\ClassWithToString();

(object) new \stdClass();
Expand All @@ -20,8 +20,8 @@ function (
(int) "123"; // ok
(int) "blabla";

(int) new \stdClass();
(float) new \stdClass();
(int) new \stdClass(); // error: Cannot cast stdClass to int.
(float) new \stdClass(); // error: Cannot cast stdClass to float.

(string) fopen('php://memory', 'r');
(int) fopen('php://memory', 'r');
Expand All @@ -32,20 +32,20 @@ function (
) {
/** @var object $object */
$object = doFoo();
(string) $object;
(string) $object; // error: Cannot cast object to string.

if (method_exists($object, '__toString')) {
(string) $object;
}

(string) $foo;
(string) $foo; // error: Cannot cast Test\Foo to string.
if (method_exists($foo, '__toString')) {
(string) $foo;
}

/** @var array|float|int $arrayOrFloatOrInt */
$arrayOrFloatOrInt = doFoo();
(string) $arrayOrFloatOrInt;
(string) $arrayOrFloatOrInt; // error: Cannot cast array|float|int to string.
};

function (
Expand Down