Giter Club home page Giter Club logo

psalm-plugin-phpunit's People

Contributors

androlgenhald avatar caugner avatar danog avatar ddeboer avatar dependabot[bot] avatar enumag avatar ktomk avatar midnightdesign avatar mougrim avatar mr-feek avatar muglug avatar orklah avatar radarlog avatar sander-bol avatar signpostmarv avatar snapshotpl avatar vincentlanglet avatar vv12131415 avatar weirdan avatar weph avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

psalm-plugin-phpunit's Issues

[Prophecy][False Positive] Argument::that problems

InvalidArgument - tests/Feature/Cart/CartItems/GSuiteCartItemTest.php:87:122 - Argument 1 of Prophecy\Argument::that expects callable():bool, Closure(mixed):bool provided

The callable passed to Argument::that is allowed to have parameters I believe. If I delete the stub entirely, my test passes however then we lose the benefit of the bool typehint.

How do we inform psalm that any # of mixed params can be parameters of the callable?

How to use plugin along with Symfony's PHPUnit bridge?

Since Symfony PHPUnit bridge installs PHPUnit in separate directory (not with other vendors) after installing Psalm's PHPUnit plugin I have 2 different versions of PHPUnit installed in the project (and my tests fail, which is different story..).

I would like Psalm to use already installed PHPUnit, with just proper configuration / bootstrap files.

How to achieve that?

Fails when test method default values are given as consts

Just wondering what the best approach is to using default arguments on a test method & documenting the return type with a data provider, as I'm currently getting TooFewArguments .... expecting at least 3, but saw 2 provided by .... ::dataprovider_markuparraytomarkupstring():(array<int, array{0:string, 1:array<array-key, mixed>}>), where the test method is something like:

public function test_thing(
    string $expected,
    array $source,
    bool $as_xml = SomeOtherClassBecauseMutationTesting::DEFAULT_BOOL_AS_XML,
    int $flags = Etc::DEFAULT_YOU_GET_THE_IDEA) {
...
}

and the data provider has an array return but only a couple of the inner arrays include some of the extra args?

I'm thinking typed args is better than shoving in a mixed variadic & when I tried doing @psalm-return array<int, array{0:string, 1:array, 2?:bool, 3?:int}> it didn't exactly work as expected.

InvocationMocker::method has been marked as internal

I am unsure whether this is right here. I am using psalm, phpunit and this plugin together. PHPUnit\Framework\MockObject\Builder\InvocationMocker is marked as internal but it is the way to mock objects. So when I combine them I have to start mocking everything manually?

TestCase::assertTrue(is_a(str, str, true)), TestCase::assertInstanceOf() & TypeCoercion, InvalidStringClass

How cumbersome would it be for this plugin to decorate the variables being asserted with appropriate flags?

i.e. TestCase::assertInstanceOf(class-string, $foo) prior to Bar($foo) where the method signature is Bar(WhateverTheClassStringWasInTheAssertion $obj) and $foo is of type WhateverTheClassStringWasInTheAssertion|null results in Argument of Bar cannot be null, possibly null value provided`

Similarly, when asserting that is_a() returns true, psalm is not made aware that string $foo is now class-string<SomeThing> $foo, triggering InvalidStringClass on invocation and TypeCoercion when $foo is later passed argument 1 to TestCase::assertInstanceOf():

function testThing(string $foo) : void {
    static::assertTrue(is_a($foo, SomeThing::class, true));
    $obj = new $type();
    /* actual test stuff here */
}

What is the correct way to work with ObjectProphecy

Hi,
We have a lot of tests in our Symfony project which basically look like this

class SomeServiceTest extends TestCase
{
    use ProphecyTrait;

    /**
     * @var OtherServiceInterface|ObjectProphecy
     */
    private $otherService;

    private SomeService $someService;

    protected function setUp(): void
    {
        $this->otherService = $this->prophesize(OtherServiceInterface::class);
        
		$this->someService = new SomeService(
            $this-otherService->reveal()
        );
    }

...

}

But in tests we get error when we try to use willReturn() method from Prophecy

	$this-otherService->method()->willReturn($something);

we get:

psalm: PossiblyUndefinedMethod: Method OtherService::willReturn does not exist

Isn't the union type in the annotation states that both classes are valid for this property?

Trait analysis

Currently the plugin does not validate test methods and providers in traits, that are used in test cases.

Example:

  Scenario: Test methods and providers in trait used by a test case are validated
    Given I have the following code
      """
      trait MyTestTrait {
        /**
         * @return void
         * @dataProvider provide
         */
        public function testSomething(string $_p) {}
        /**
         * @return iterable<int,int[]>
         */
        public function provide() { return [[1]]; }
      }

      class MyTestCase extends TestCase {
        use MyTestTrait;
      }
      """
    When I run Psalm
    Then I see these errors
      | Type            | Message                                                                                                                                      |
      | InvalidArgument | Argument 1 of NS\MyTestTrait::testSomething expects string, int provided by NS\MyTestTrait::provide():(iterable<int, array<array-key, int>>) |
    And I see no other errors

Actual: no issues reported

False `DeprecatedMethod` positives for `expects`

Not sure if this is a psalm issue but thought I'd come here to clarify how stubs work.
Using PHPUnit 7 which has

    /**
     * Registers a new expectation in the mock object and returns the match
     * object which can be infused with further details.
     *
     * @return InvocationMocker
     */
    public function expects(Invocation $matcher);

in MockObject.
This doesn't seem to appear in the stub but no error in IDE (possibly because psalm warning level is not high enough?)

When calling createMock(MyClass::class) and deprecating __call on MyClass, I get errors about DeprecatedMethod for uses of expects.
Is that expected behaviour or is something incorrect?

MissingConstructor on TestCase grandchild

  Scenario: Stateful grandchild test case with setUp produces no MissingConstructor
    Given I have the following code
      """
      use Prophecy\Prophecy\ObjectProphecy;

      class BaseTestCase extends TestCase {}

      interface I { public function work(): int; }

      class MyTestCase extends BaseTestCase
      {
        /** @var ObjectProphecy<I> */
        private $i;

        /** @return void */
        public function setUp() {
          $this->i = $this->prophesize(I::class);
        }

        /** @return void */
        public function testSomething() {
          $this->i->work()->willReturn(1);;
          $i = $this->i->reveal();
          $this->assertEquals(1, $i->work());
        }
      }
      """
    When I run Psalm
    Then I see no errors

Actual: MissingConstructor is reported on MyTestCase

Support for external data providers

Data providers can be specified like this:

/**
 * @dataProvider \Ergebnis\License\Test\Util\DataProvider\Text::blankOrEmptyString()
 *
 * @param string $name
 */
public function testFromFileRejectsBlankOrEmptyFileName(string $name): void
{
    $this->expectException(Exception\InvalidFile::class);

    Template::fromFile($name);
}

However, psalm/phpunit-psalm-plugin complains with

ERROR: UndefinedMethod - test/Unit/HolderTest.php:33:21 - Provider method Ergebnis\License\Test\Unit\HolderTest::\Ergebnis\License\Test\Util\DataProvider\Text::blankOrEmptyString() is not defined
    public function testFromStringRejectsBlankOrEmptyValue(string $value): void


ERROR: UndefinedMethod - test/Unit/TemplateTest.php:67:21 - Provider method Ergebnis\License\Test\Unit\TemplateTest::\Ergebnis\License\Test\Util\DataProvider\Text::blankOrEmptyString() is not defined
    public function testFromStringRejectsBlankOrEmptyFileName(string $name): void

Is there a chance this can be supported as well?

For a concrete example, see ergebnis/license#23.

query: re code coverage support

Is it feasible for this plugin to parse phpunit coverage files to prevent PossiblyUnusedMethod when calling psalm --find-dead-code from being flagged up if the method is present as being covered in the coverage file?

Uncaught Exception: Could not locate trait statement

It is unable to find Symfony\Component\Validator\Test\ForwardCompatTestTrait

 540 / 796 (67%)
░░░░Uncaught Exception: Could not locate trait statement
Stack trace in the forked worker:
#0 vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(114): Psalm\Internal\Codebase\ClassLikes->getTraitNode('Symfony\\Compone...')
#1 vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(1062): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::afterStatementAnalysis(Object(PhpParser\Node\Stmt\Class_), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Internal\Analyzer\ClassAnalyzer), Object(Psalm\Codebase), Array)
#2 vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(201): Psalm\Internal\Analyzer\ClassAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#3 vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(318): Psalm\Internal\Analyzer\FileAnalyzer->analyze(NULL)
#4 vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(182): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(105, '')
#5 vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(435): Psalm\Internal\Fork\Pool->__construct(Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#6 vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(241): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 7)
#7 vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(528): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 7, false)
#8 vendor/vimeo/psalm/src/psalm.php(594): Psalm\Internal\Analyzer\ProjectAnalyzer->check('', false)
#9 vendor/vimeo/psalm/psalm(2): require_once('')
#10 {main} in vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php:339
Stack trace:
#0 vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(371): Psalm\Internal\Fork\Pool->readResultsFromChildren()
#1 vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(444): Psalm\Internal\Fork\Pool->wait()
#2 vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(241): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 7)
#3 vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(528): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 7, false)
#4 vendor/vimeo/psalm/src/psalm.php(594): Psalm\Internal\Analyzer\ProjectAnalyzer->check('', false)
#5 vendor/vimeo/psalm/psalm(2): require_once('')
#6 {main}
(Psalm 3.9.4@352bd3f5c5789db04e4010856c2f4e01ed354f4e crashed due to an uncaught Throwable)

Versions

psalm/plugin-phpunit: 0.9.0
vimeo/psalm: 3.9.4
weirdan/doctrine-psalm-plugin: 0.10.0

Unknown psalm annotation should report an issue

If we use not supported / unknown psalm annotation, for example instead of @psalm-suppress by mistake we use @psalm-ignore which is not a valid existing annotation, psalm should report that as an issue rather than throwing exception. Because of exception it is checking whole codebase instead of the files changed in the branch and throws other random errors

[RUNNING] [PHP-DEPENDENCIES] .cache/php/Psalm.x 
Checking files
PHP Fatal error:  Uncaught Psalm\Exception\DocblockParseException: Unrecognised annotation @psalm-ignore in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/DocComment.php:156
Stack trace:
#0 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(528): Psalm\DocComment::parse('This method sea...', 57)
#1 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(518): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::getSpecials(Object(PhpParser\Node\Stmt\ClassMethod))
#2 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(509): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::isBeforeInitializer(Object(PhpParser\Node\Stmt\ClassMethod))
#3 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(90): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::hasInitializers(Object(Psalm\Storage\ClassLikeStorage), Object(PhpParser\Node\Stmt\Class_))
#4 /Users/sourav/Documents/work/ in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/DocComment.php on line 156

Fatal error: Uncaught Psalm\Exception\DocblockParseException: Unrecognised annotation @psalm-ignore in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/DocComment.php:156
Stack trace:
#0 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(528): Psalm\DocComment::parse('This method sea...', 57)
#1 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(518): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::getSpecials(Object(PhpParser\Node\Stmt\ClassMethod))
#2 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(509): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::isBeforeInitializer(Object(PhpParser\Node\Stmt\ClassMethod))
#3 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/psalm/plugin-phpunit/hooks/TestCaseHandler.php(90): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::hasInitializers(Object(Psalm\Storage\ClassLikeStorage), Object(PhpParser\Node\Stmt\Class_))
#4 /Users/sourav/Documents/work/ in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/DocComment.php on line 156
PHP Fatal error:  Uncaught InvalidArgumentException: Could not get file storage for /users/sourav/documents/work/vimeo/vimeo/routes/main.php in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php:49
Stack trace:
#0 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(141): Psalm\Internal\Provider\FileStorageProvider->get('/users/sourav/d...')
#1 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(262): Psalm\Internal\Analyzer\FileAnalyzer->analyze(NULL)
#2 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(182): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(10, '/Users/sourav/D...')
#3 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(354): Psalm\Internal\Fork\Pool->__construct(Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#4 / in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php on line 49

Fatal error: Uncaught InvalidArgumentException: Could not get file storage for /users/sourav/documents/work/vimeo/vimeo/routes/main.php in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php:49
Stack trace:
#0 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(141): Psalm\Internal\Provider\FileStorageProvider->get('/users/sourav/d...')
#1 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(262): Psalm\Internal\Analyzer\FileAnalyzer->analyze(NULL)
#2 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(182): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(10, '/Users/sourav/D...')
#3 /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(354): Psalm\Internal\Fork\Pool->__construct(Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#4 / in /Users/sourav/Documents/work/Vimeo/vimeo/vendor/vimeo/psalm/src/Psalm/Internal/Provider/FileStorageProvider.php on line 49

Data provider return type

I have a phpunit test with data provider but I can't understand how to fix this errors:

MismatchingDocblockReturnType 1

MixedInferredReturnType 2

PossiblyInvalidArgument 3

class MyTest extends TestCase
{
  
    /**
     * @psalm-return list<array-key, array<array-key, mixed>> **1** **2**
     */
    public function dataProvider(): array
    {
        return [
            [      [],   "message...'"    ],
            [    ['invalid' => true],    "message" ],
            [    ['foo' => 'value'],      "message" ],
        ];
    }

    /**
     * @test
     * @dataProvider dataProvider
     */
    public function my_test(array $payload, string $errorMessage): void **3**
    {
       
    }
}

Travis build status is not reported

@muglug it seems Travis stopped reporting build status to github some time ago, even though the builds are actually performed. I don't have sufficient permissions on this repo to investigate - can you check if there's anything wrong with Travis integration?

Debug --diff --diff-methods creates false-positives

On Psalm latest

./psalm --clear-cache
git checkout b3f6b56
./psalm --threads=8 --diff --diff-methods --show-info=false
./psalm --threads=8 --diff --diff-methods --show-info=false
git checkout 6f6e265
./psalm --threads=8 --diff --diff-methods --show-info=false

Expected: No issues
Actual: A whole bunch of PossiblyUnusedMethod issues that would normally be covered by the PHPUnit plugin

This is probably a bug in Psalm, rather than the plugin, but storing here because it affects the plugin.

calling assertInstanceOf on a property set in TestCase::setUp() should result in RedundantCondition

  • if TestCase::$foo is T|null
  • and TestCase::setUp() sets TestCase::$foo to T
  • and nothing else modifies TestCase::$foo
  • then TestCase::assertInstanceOf(T::class, $this->foo) is redundant

not quite sure on the exact error/test case that should occur, but this is a vague mashup of what I'm thinking:

  Scenario: setUp resolves types
    Given I have the following code
      """
      class MyTestCase extends TestCase
      {
        /** @var \DateTime|null */
        private $date;

        /** @return void */
        public function setUp() {
          $this->date = new \DateTime();
        }

        /** @return void */
        public function testSomething() {
          $this->assertInstanceOf(\DateTime::class, $this->date);
        }
      }
      """
    When I run Psalm
    Then I see these errors
      | Type            | Message                                                                                                     |
      | RedundantConditionGivenDocblockType | Found a redundant condition when evaluating docblock-defined type $this->date and trying to reconcile type 'DateTime' to DateTime |
    And I see no other errors

Uncaught Exception

Whilst running Psalm with the PHPUnit plugin on my project, at some point I caused it to break hard although I'm not sure from what exactly:

Full stack trace:

PHP Warning:  assert(): assert(null !== $param->type) failed in /path/to/project/vendor/psalm/plugin-phpunit/src/Hooks/TestCaseHandler.php on line 397
Warning: assert(): assert(null !== $param->type) failed in /path/to/project/vendor/psalm/plugin-phpunit/src/Hooks/TestCaseHandler.php on line 397
░Uncaught Exception: Argument 2 passed to Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUnitPlugin\Hooks\{closure}() must be an instance of Psalm\Type\Union, null given, called in /path/to/project/vendor/psalm/plugin-phpunit/src/Hooks/TestCaseHandler.php on line 415
Stack trace in the forked worker:
#0 /path/to/project/vendor/psalm/plugin-phpunit/src/Hooks/TestCaseHandler.php(415): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUnitPlugin\Hooks\{closure}(Object(Psalm\Type\Union), NULL, false, 0)
#1 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ClassAnalyzer.php(963): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::afterStatementAnalysis(Object(PhpParser\Node\Stmt\Class_), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Internal\Analyzer\ClassAnalyzer), Object(Psalm\Codebase), Array)
#2 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/FileAnalyzer.php(211): Psalm\Internal\Analyzer\ClassAnalyzer->analyze(Object(Psalm\Context), Object(Psalm\Context))
#3 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(340): Psalm\Internal\Analyzer\FileAnalyzer->analyze(NULL)
#4 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(193): Psalm\Internal\Codebase\Analyzer->Psalm\Internal\Codebase\{closure}(4, '/Users/tfidry/P...')
#5 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(406): Psalm\Internal\Fork\Pool->__construct(Array, Object(Closure), Object(Closure), Object(Closure), Object(Closure))
#6 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(269): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 3)
#7 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(639): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 3, false, true)
#8 /path/to/project/vendor/vimeo/psalm/src/psalm.php(676): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/tfidry/P...', true)
#9 /path/to/project/vendor/vimeo/psalm/psalm(2): require_once('/Users/tfidry/P...')
#10 {main} in /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php:357
Stack trace:
#0 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Fork/Pool.php(389): Psalm\Internal\Fork\Pool->readResultsFromChildren()
#1 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(473): Psalm\Internal\Fork\Pool->wait()
#2 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Codebase/Analyzer.php(269): Psalm\Internal\Codebase\Analyzer->doAnalysis(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 3)
#3 /path/to/project/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php(639): Psalm\Internal\Codebase\Analyzer->analyzeFiles(Object(Psalm\Internal\Analyzer\ProjectAnalyzer), 3, false, true)
#4 /path/to/project/vendor/vimeo/psalm/src/psalm.php(676): Psalm\Internal\Analyzer\ProjectAnalyzer->check('/Users/tfidry/P...', true)
#5 /path/to/project/vendor/vimeo/psalm/psalm(2): require_once('/Users/tfidry/P...')
#6 {main}
(Psalm 4.3.1@2feba22a005a18bf31d4c7b9bdb9252c73897476 crashed due to an uncaught Throwable)
make: *** [psalm] Error 1

PHPUnit 9.4.4 & Psalm 4.3.1

Erroneous TooFewArguments on combination of data provider and dependent test

In my case, test case class has the following structure:

<?php

class MyTest extends \PHPUnit\Framework\TestCase
{

    public function testOne(): string
    {
        // ...
        return 'bar';
    }

    /**
     * @dataProvider providerFoo
     * @depends testOne
     */
    public function testSecond(string $foo, string $bar): void
    {
        // ...
    }

    /**
     * @return iterable<string, array{string}>
    public function providerFoo(): iterable
    {
        return [['foo']];
    }
}

In this case, testSecond() method gets $foo = 'foo' and $bar = 'bar' arguments; but Psalm reports TooFewArguments (probably ignoring dependency if data provider is set).

P.S.: Anyone please tell me how to suppress this bug until it gets fixed. Adding @psalm-suppress TooFewArguments both in provider or test method docblocks doesn't help.

why `packageVersionIs` method exists?

I have not found usages of this private method

private function packageVersionIs(string $package, string $op, string $ref): bool
{
$currentVersion = (string) explode('@', Versions::getVersion($package))[0];
$parser = new VersionParser();
$currentVersion = $parser->normalize($currentVersion);
$ref = $parser->normalize($ref);
return Comparator::compare($currentVersion, $op, $ref);
}

May be it needs to be deleted?
Also if deleting this, also delete composer/semver dependency composer remove composer/semver because it's used only there

Missing XML output when using this plugin

Found that issue while working on doctrine/sql-formatter#51 (comment)

With the plugin loaded in psalm.xml:

vendor/bin/psalm --output-format=checkstyle
Scanning files...
Analyzing files...

░░░░░░░░░░░░░

Without:

Scanning files...
Analyzing files...

░░░░░░░░░░░░░
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle>
</checkstyle>

Why is there no checkstyle output in the former?

Support for inline data providers declaration

I'm getting UndefinedMethod and PossiblyUnusedMethod false positives when I declare my data providers like this:

/** @dataProvider getTestCases */

Because the parser adds an extra space a the end of the method name.
This can be fixed by changing the following line:

-$apparent_provider_method_name = $class_storage->name . '::' . (string) $provider;
+$apparent_provider_method_name = $class_storage->name . '::' . trim((string) $provider);

But this doesn't fix the PossiblyUnusedMethod error for that constructor.

Any ideas how to properly fix this?

PS: I wanted to send a PR with a test, but I can't get composer test to run without errors on my machine before doing my changes. Is this a known issue (test failing) that I can ignore or am I doing something wrong?

class_alias business causing fatal error

This is sort of similar to the issue I ran into with the Mockery plugin (vimeo/psalm#1537) in that a class_alias is being used to conditionally alias one class to another. In this case, it's Zend Framework's zend-test that's doing it:

https://github.com/zendframework/zend-test/blob/master/autoload/phpunit-class-aliases.php#L15

use PHPUnit\Framework\ExpectationFailedException;
use PHPUnit\Framework\TestCase;
if (! class_exists(ExpectationFailedException::class)) {
    class_alias(\PHPUnit_Framework_ExpectationFailedException::class, ExpectationFailedException::class);
}
if (! class_exists(TestCase::class)) {
    class_alias(\PHPUnit_Framework_TestCase::class, TestCase::class);
}

That second if condition is causing Psalm, with this phpunit plugin enabled, to spit out this Fatal error:

Fatal error: Uncaught InvalidArgumentException: Could not get class storage for PHPUnit_Framework_TestCase in ./vendor/vimeo/psalm/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php:44

In my case \PHPUnit_Framework\TestCase does not exist, as we're using PHPUnit 7.x which does not have that class defined. However, PHPUnit\Framework\TestCase should exist, but perhaps it's not seen by Psalm when analyzing this autoloaded file?

Without the plugin enabled, the error is not thrown, but Psalm does complain about Zend's classes (which we extend) depending on that class (PHPUnit_Framework_TestCase) and the class not existing.

If I comment out that second if condition in the file I link to above, all processes as expected.

Is this something that this plugin should deal with? Perhaps as an addition to the stubs?

New release

There's been quite a few changes since last release, could we have a new one?

The draft release is already there, please take a look.

`MissingConstructor` issues detected when explicitly specifying a test code analysis path

While working on a project that uses very strict psalm settings and this plugin, we discovered that running vendor/bin/psalm with following configuration and no arguments, no issues are reported:

<?xml version="1.0"?>
<psalm
    totallyTyped="true"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src"/>
        <directory name="tests/src"/>
        <ignoreFiles>
            <directory name="vendor"/>
        </ignoreFiles>
    </projectFiles>

    <issueHandlers>
        <InternalMethod>
            <errorLevel type="suppress">
                <referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::willReturnCallback"/>
                <referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::willReturn"/>
                <referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::method"/>
                <referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::with"/>
                <referencedMethod name="PHPUnit\Framework\MockObject\Builder\InvocationMocker::withConsecutive"/>
            </errorLevel>
        </InternalMethod>
    </issueHandlers>

    <plugins>
        <pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
    </plugins>
</psalm>

If I run vendor/bin/psalm tests (new argument added!), the output changes, and starts reporting stuff that is generally silenced by this plugin (the following, but for every test):

ERROR: MissingConstructor - tests/src/Unit/Presentation/UserViewTest.php:27:35 - SomeProject\Core\Tests\Unit\Presentation\UserViewTest has an uninitialized property $this->user, but no constructor (see https://psalm.dev/073)
    private UserSettingRepository $userSettingRepository;

Interestingly, no other errors around assertions nor anything: it seems like the translation from setUp to __construct isn't really working as expected 🤔

Tried it on updated dependencies BTW:

psalm/plugin-phpunit 0.12.2 Psalm plugin for PHPUnit
vimeo/psalm          3.17.2 A static analysis tool for finding errors in PHP applications

Obscure errors thrown

I had a provider with this signature:

public function providePropertyValue(): ?Generator

which although is not recommended is not incorrect either. The result was:

Uncaught Exception: should be iterable
Stack trace in the forked worker:
/path/to/vendor/psalm/plugin-phpunit/src/Hooks/TestCaseHandler.php

I think there is two issues here:

  • the above should not result in a crash
  • I can see 3 RuntimeException thrown in TestCaseHandler which a simple message: "Unexpected: union has no way to get it constituent types", "should be iterable", "unexpected type": IMO it's not very user-friendly to throw those exceptions like without any more context... IMO requiring the user to have xdebug, disallow the xdebug handler (all of that is harder if using the PHAR too) and then debugging this just to be able to report the issue is making it a bit harder than necessary...

Returning Generator from data provider

According to PHPUnit docs:

A data provider method must be public and either return an array of arrays or an object that implements the Iterator interface and yields an array for each iteration step.

I'm returning Generator

/** @return Generator<list<mixed>> */
public function providerX() : Generator

plugin complains that

ERROR: MixedInferredReturnType - tests/MyTest.php:xx:yy - Providers must return iterable<array-key, array<array-key, mixed>>, possibly different Generator<mixed, list, mixed, mixed> provided (see https://psalm.dev/047)

I'd expect no error for this.

Suppress UnusedFunctionCall, UnusedMethodCall when test expects an exception

Consider this case:

<?php

use PHPUnit\Framework\TestCase;

final class JsonDecodeTest extends TestCase
{
    public function testThrowsOnInvalidJson(): void
    {
        $this->expectException(\JsonException::class);

        // UnusedFunctionCall is thrown here
        json_decode('', flags: JSON_THROW_ON_ERROR);
    }
}

I am not sure that this is doable or worth the effort, so feel free to close the issue. For now we just suppressed UnusedFunctionCall and UnusedMethodCall in psalm.xml for the whole tests directory.

Descendant of a class with initializers should produce no MissingConstructor

  Scenario: Descendant of a test that has setUp produces no MissingConstructor
    Given I have the following code
      """
      use Prophecy\Prophecy\ObjectProphecy;

      interface I { public function work(): int; }

      class BaseTestCase extends TestCase {
        /** @var ObjectProphecy<I> */
        protected $i;

        /** @return void */
        public function setUp() {
          $this->i = $this->prophesize(I::class);
        }
      }

      class MyTestCase extends BaseTestCase
      {
        /** @return void */
        public function testSomething() {
          $this->i->work()->willReturn(1);;
          $i = $this->i->reveal();
          $this->assertEquals(1, $i->work());
        }
      }
      """
    When I run Psalm
    Then I see no errors

Actual:

Type Message
MissingConstructor NS\MyTestCase has an uninitialized variable $this->i, but no constructor

PHPUnit setup methods should be treated equally to constructors

PHPUnit executes setUp() and other @before methods on each test run, so with regards to the test (test*() and @test) methods they behave more or less like a constructor.

When analyzing test classes, Psalm and the PHPUnit plugin report the properties initialized in setup methods as not initialized in constructor.

$ composer show | grep psalm
psalm/plugin-phpunit                           0.15.1    Psalm plugin for PHPUnit
vimeo/psalm                                    4.5.2     A static analysis tool for find...
<?php

namespace Tests;

use PHPUnit\Framework\TestCase;
use stdClass;

class ObjectTest extends TestCase
{
    /** @var stdClass */
    private $object;

    protected function setUp(): void
    {
        $this->object = new stdClass();
    }

    public function testObject(): void
    {
        self::assertIsNotNull($this->object);
    }
}
$ psalm tests/ObjectTest.php

ERROR: PropertyNotSetInConstructor - tests/ObjectTest.php:11:13 - PropertyTests\ObjectTest::$object is not defined in constructor of Tests\ObjectTest and in any private or final methods called in the constructor (see https://psalm.dev/074)
    private $object;

While it's technically true, it's irrelevant for the test cases. By the time when the test method is invoked, the property will be initialized.

It is possible to rework such tests to initialize the object explicitly for each test method but this is less convenient, more verbose and in fact invalidates the PHPUnit setup approach.

MissingDependency in phpunit dependencies

The following error occours:

ERROR: MissingDependency - tests/myTest.php - PHPUnit\Framework\MockObject\Builder\InvocationMocker depends on class or interface phpunit\framework\mockobject\builder\parametersmatch that does not exist (see https://psalm.dev/157)
        $aMock->expects($this->never())

The components from error

vendor/phpunit/phpunit/src/Framework/MockObject/Builder/
├── Identity.php
├── InvocationMocker.php
├── InvocationStubber.php
├── Match.php
├── MethodNameMatch.php
├── ParametersMatch.php
└── Stub.php

0 directories, 7 files

Psalm.xml config

    <projectFiles>
        <directory name="src"/>
        <directory name="tests"/>
        <ignoreFiles>
            <directory name="vendor"/>
        </ignoreFiles>
    </projectFiles>
    <plugins>
        <pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
    </plugins>

Info

  • Psalm version: Psalm 3.12.2@7c7ebd068f8acaba211d4a2c707c4ba90874fa26
  • PHPUnit version: "phpunit/phpunit": "^9.1.4"
  • PHPUnit plugin psalm: "psalm/plugin-phpunit": "^0.10.1"

Add support for `assertArrayHasKey` and `assertCount`

This code is valid https://psalm.dev/r/e1d61bda01

But when I use assertArrayHasKey and assertCount, the offset are reported as possibly undefined.
It would be great to tell psalm that

assertArrayHasKey work as `array_key_exists` so the offset is not undefined for arrays
assertCount work as `count` so the offset is not undefined for list

I'm not familiar with, but I'm not sure it can be done with psalm assertion https://psalm.dev/docs/annotating_code/assertion_syntax/

Work properly in multithreaded mode

With latest Psalm master one can just do

$codebase->methodExists($declaring_method_id, null, 'PHPUnit\Framework\TestSuite::run');

to reliably register a method as having been used, but looks like that doesn't work elsewhere

fatal error w/ assert

using:

  • daft-markup\Tests\ValidatorTest.php:295

  • daft-markup\Tests\ValidatorTest.php:297

  • PHP Warning: assert(): assert(null !== $param->type) failed in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 351

  • PHP Fatal error: Uncaught TypeError: Argument 2 passed to Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUn

  • Warning: assert(): assert(null !== $param->type) failed in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 351
    itPlugin\Hooks{closure}() must be an instance of Psalm\Type\Union, null given, called in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 370 and defined in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php:263

  • Stack trace:

    • #​0 daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php(370): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUnitPlugin\Hooks{closure}(Object(Psalm\Type\Union), NULL, false, 0)
    • #​1 daft-markup\vendor\vimeo\psalm\src\Psalm\Internal\Analyzer\ClassAnalyzer.php(942): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::afterStatementAnalysis(Object(PhpParser\Node\Stmt\Class_), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Internal\Analyzer\NamespaceAnalyzer), Object(Psalm\Codebase), Array)
    • #​2 daft-markup\vendor\vimeo\psalm\src\Psal in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 263
  • Fatal error: Uncaught TypeError: Argument 2 passed to Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUnitPlugin\Hooks{closure}() must be an instance of Psalm\Type\Union, null given, called in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 370 and defined in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php:263

  • Stack trace:

    • #​0 daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php(370): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::Psalm\PhpUnitPlugin\Hooks{closure}(Object(Psalm\Type\Union), NULL, false, 0)
    • #​1 daft-markup\vendor\vimeo\psalm\src\Psalm\Internal\Analyzer\ClassAnalyzer.php(942): Psalm\PhpUnitPlugin\Hooks\TestCaseHandler::afterStatementAnalysis(Object(PhpParser\Node\Stmt\Class_), Object(Psalm\Storage\ClassLikeStorage), Object(Psalm\Internal\Analyzer\NamespaceAnalyzer), Object(Psalm\Codebase), Array)
    • #​2 daft-markup\vendor\vimeo\psalm\src\Psal in daft-markup\vendor\psalm\plugin-phpunit\hooks\TestCaseHandler.php on line 263

https://github.com/SignpostMarv/daft-markup/blob/02448009310e961520e5a50beadd8fc8b97c64be/Tests/ValidatorTest.php#L297

    /**
    * @param mixed $markupContent
    * @param class-string<\Throwable> $expected_message
    *
    * @dataProvider dataProvider_ValidateContent_failure
    */
    public function test_ValidateContent_failure(
        $markup_content,
        string $expected_exception,
        string $expected_message
    ) : void {
        static::expectException($expected_exception);
        static::expectExceptionMessage($expected_message);

        MarkupValidator::ValidateContent($markup_content);
    }

https://github.com/SignpostMarv/daft-markup/blob/02448009310e961520e5a50beadd8fc8b97c64be/src/MarkupValidator.php#L125

    /**
    * @param mixed $markupContent
    */
    final public static function ValidateContent($markupContent) : void
    {
        if ( ! is_array($markupContent)) {
            throw new InvalidArgumentException('Element content must be specified as an array!');
        }

        /**
        * @var array<int|string, mixed>
        */
        $markupContent = $markupContent;

        foreach (array_keys($markupContent) as $key) {
            if ( ! is_scalar($markupContent[$key]) && ! is_array($markupContent[$key])) {
                throw new InvalidArgumentException('Element content must be scalar or an array!');
            }
        }
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.