2016-12-12 05:41:11 +01:00
< ? php
namespace Psalm\Tests ;
2018-02-01 07:10:27 +01:00
use Psalm\Config ;
use Psalm\Context ;
2021-12-03 20:29:06 +01:00
use Psalm\Exception\CodeException ;
2021-12-04 21:55:53 +01:00
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait ;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait ;
2018-02-01 07:10:27 +01:00
2021-06-08 04:55:21 +02:00
use const DIRECTORY_SEPARATOR ;
2017-04-25 05:45:02 +02:00
class AnnotationTest extends TestCase
2016-12-12 05:41:11 +01:00
{
2021-12-04 21:55:53 +01:00
use InvalidCodeAnalysisTestTrait ;
use ValidCodeAnalysisTestTrait ;
2016-12-24 19:23:22 +01:00
2021-08-05 21:32:32 +02:00
public function setUp () : void
{
parent :: setUp ();
$codebase = $this -> project_analyzer -> getCodebase ();
$codebase -> reportUnusedVariables ();
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsWithValidArrayIteratorArgument () : void
2018-02-01 07:10:27 +01:00
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
2021-08-05 21:48:37 +02:00
function takesString ( string $_s ) : void {}
2018-02-01 07:10:27 +01:00
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString ( ArrayIterator $i ) : void {
$s = $i -> offsetGet ( " a " );
takesString ( $s );
foreach ( $i as $s2 ) {
takesString ( $s2 );
}
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2021-10-14 21:04:53 +02:00
public function testPhpStormGenericsWithTypeInSignature () : void
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
function a ( array | \ArrayObject $_meta = []) : void {} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsWithValidTraversableArgument () : void
2019-02-15 17:07:08 +01:00
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
2021-08-05 21:48:37 +02:00
function takesString ( string $_s ) : void {}
2019-02-15 17:07:08 +01:00
/** @param Traversable|string[] $i */
function takesTraversableOfString ( Traversable $i ) : void {
foreach ( $i as $s2 ) {
takesString ( $s2 );
}
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsWithClassProperty () : void
2018-03-17 20:40:57 +01:00
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
/** @psalm-suppress MissingConstructor */
class Foo {
2020-03-22 15:44:48 +01:00
/** @var \stdClass[]|\ArrayObject */
public $bar ;
/**
* @ return \stdClass [] | \ArrayObject
*/
public function getBar () : \ArrayObject {
return $this -> bar ;
}
2018-03-17 20:40:57 +01:00
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsWithGeneratorArray () : void
2019-02-20 17:13:33 +01:00
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
class A {
/**
* @ return stdClass [] | Generator
*/
function getCollection () : Generator
{
yield new stdClass ;
}
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsWithValidIterableArgument () : void
2018-03-01 17:19:23 +01:00
{
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
2021-08-05 21:48:37 +02:00
function takesString ( string $_s ) : void {}
2018-03-01 17:19:23 +01:00
/** @param iterable|string[] $i */
function takesArrayIteratorOfString ( iterable $i ) : void {
foreach ( $i as $s2 ) {
takesString ( $s2 );
}
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsInvalidArgument () : void
2018-02-01 07:10:27 +01:00
{
2021-12-03 20:29:06 +01:00
$this -> expectException ( CodeException :: class );
2019-05-17 00:36:36 +02:00
$this -> expectExceptionMessage ( 'InvalidScalarArgument' );
2018-02-01 07:10:27 +01:00
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
2021-08-05 21:32:32 +02:00
function takesInt ( int $_s ) : void {}
2018-02-01 07:10:27 +01:00
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString ( ArrayIterator $i ) : void {
$s = $i -> offsetGet ( " a " );
takesInt ( $s );
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testPhpStormGenericsNoTypehint () : void
2018-02-01 07:10:27 +01:00
{
2021-12-03 20:29:06 +01:00
$this -> expectException ( CodeException :: class );
2019-05-17 00:36:36 +02:00
$this -> expectExceptionMessage ( 'PossiblyInvalidMethodCall' );
2018-02-01 07:10:27 +01:00
Config :: getInstance () -> allow_phpstorm_generics = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString ( $i ) : void {
$s = $i -> offsetGet ( " a " );
} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testInvalidParamDefault () : void
2018-06-07 18:23:10 +02:00
{
2021-12-03 20:29:06 +01:00
$this -> expectException ( CodeException :: class );
2019-05-17 00:36:36 +02:00
$this -> expectExceptionMessage ( 'InvalidParamDefault' );
2018-06-07 18:23:10 +02:00
$this -> addFile (
'somefile.php' ,
' < ? php
/**
* @ param array $arr
* @ return void
*/
function foo ( $arr = false ) {} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testInvalidParamDefaultButAllowedInConfig () : void
2018-06-07 18:23:10 +02:00
{
Config :: getInstance () -> add_param_default_to_docblock_type = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
/**
2021-08-05 21:48:37 +02:00
* @ param array $_arr
2018-06-07 18:23:10 +02:00
* @ return void
*/
2021-08-05 21:48:37 +02:00
function foo ( $_arr = false ) {}
2018-06-07 18:23:10 +02:00
foo ( false );
foo ([ " hello " ]); '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2020-09-12 17:24:05 +02:00
public function testInvalidTypehintParamDefaultButAllowedInConfig () : void
2018-06-07 18:23:10 +02:00
{
2021-12-03 20:29:06 +01:00
$this -> expectException ( CodeException :: class );
2019-05-17 00:36:36 +02:00
$this -> expectExceptionMessage ( 'InvalidParamDefault' );
2018-06-07 18:23:10 +02:00
Config :: getInstance () -> add_param_default_to_docblock_type = true ;
$this -> addFile (
'somefile.php' ,
' < ? php
function foo ( array $arr = false ) : void {} '
);
$this -> analyzeFile ( 'somefile.php' , new Context ());
}
2017-03-19 19:39:05 +01:00
/**
2019-03-01 21:55:20 +01:00
* @ return iterable < string , array { string , assertions ? : array < string , string > , error_levels ? : string []} >
2017-04-25 05:45:02 +02:00
*/
2020-09-12 17:24:05 +02:00
public function providerValidCodeParse () : iterable
2017-04-25 05:45:02 +02:00
{
return [
2018-01-21 16:22:04 +01:00
'nopType' => [
' < ? php
2021-08-05 21:48:37 +02:00
$_a = " hello " ;
2018-01-21 16:22:04 +01:00
2021-08-05 21:48:37 +02:00
/** @var int $_a */ ' ,
2018-01-21 16:22:04 +01:00
'assertions' => [
2021-08-05 21:48:37 +02:00
'$_a' => 'int' ,
2018-01-21 16:22:04 +01:00
],
],
2017-04-25 05:45:02 +02:00
'validDocblockReturn' => [
' < ? php
/**
* @ return string
*/
2018-01-11 21:50:45 +01:00
function fooFoo () : string {
2017-04-25 05:45:02 +02:00
return " boop " ;
}
2017-05-05 00:35:05 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ return array < int , string >
*/
2018-01-11 21:50:45 +01:00
function foo2 () : array {
2017-04-25 05:45:02 +02:00
return [ " hello " ];
}
2017-05-05 00:35:05 +02:00
2017-04-25 05:45:02 +02:00
/**
* @ return array < int , string >
*/
2018-01-11 21:50:45 +01:00
function foo3 () : array {
2017-04-25 05:45:02 +02:00
return [ " hello " ];
2017-05-27 02:05:57 +02:00
} ' ,
2017-04-25 05:45:02 +02:00
],
'reassertWithIs' => [
' < ? php
/** @param array $a */
2018-01-11 21:50:45 +01:00
function foo ( $a ) : void {
2017-04-25 05:45:02 +02:00
if ( is_array ( $a )) {
// do something
}
2017-05-27 02:05:57 +02:00
} ' ,
2018-02-07 00:44:53 +01:00
'assertions' => [],
2018-02-07 21:20:47 +01:00
'error_level' => [ 'RedundantConditionGivenDocblockType' ],
2017-04-25 05:45:02 +02:00
],
'checkArrayWithIs' => [
' < ? php
/** @param mixed $b */
2018-01-11 21:50:45 +01:00
function foo ( $b ) : void {
2021-08-04 22:07:04 +02:00
/**
* @ psalm - suppress UnnecessaryVarAnnotation
* @ var array
*/
2017-04-25 05:45:02 +02:00
$a = ( array ) $b ;
if ( is_array ( $a )) {
// do something
}
2017-05-27 02:05:57 +02:00
} ' ,
2018-02-07 00:44:53 +01:00
'assertions' => [],
2018-02-07 21:20:47 +01:00
'error_level' => [ 'RedundantConditionGivenDocblockType' ],
2017-04-25 05:45:02 +02:00
],
'goodDocblock' => [
' < ? php
class A {
/**
* @ param A $a
* @ param bool $b
*/
2018-01-11 21:50:45 +01:00
public function g ( A $a , $b ) : void {
2017-04-25 05:45:02 +02:00
}
2017-05-27 02:05:57 +02:00
} ' ,
2017-04-25 05:45:02 +02:00
],
'goodDocblockInNamespace' => [
' < ? php
namespace Foo ;
2017-05-05 00:35:05 +02:00
2017-04-25 05:45:02 +02:00
class A {
/**
* @ param \Foo\A $a
* @ param bool $b
*/
2018-01-11 21:50:45 +01:00
public function g ( A $a , $b ) : void {
2017-04-25 05:45:02 +02:00
}
2017-05-27 02:05:57 +02:00
} ' ,
2017-05-05 00:35:05 +02:00
],
2017-05-10 18:36:11 +02:00
'ignoreNullableReturn' => [
' < ? php
class A {
/** @var int */
public $bar = 5 ;
2018-01-11 21:50:45 +01:00
public function foo () : void {}
2017-05-10 18:36:11 +02:00
}
/**
* @ return ? A
* @ psalm - ignore - nullable - return
*/
function makeA () {
2021-12-05 18:51:26 +01:00
return rand ( 0 , 1 ) ? new A () : null ;
2017-05-10 18:36:11 +02:00
}
2021-08-05 21:48:37 +02:00
function takeA ( A $_a ) : void { }
2017-05-10 18:36:11 +02:00
$a = makeA ();
$a -> foo ();
$a -> bar = 7 ;
2017-05-27 02:05:57 +02:00
takeA ( $a ); ' ,
2017-05-10 18:36:11 +02:00
],
2017-06-13 20:00:41 +02:00
'invalidDocblockParamSuppress' => [
' < ? php
/**
2021-08-05 21:48:37 +02:00
* @ param int $_bar
2018-01-05 18:11:12 +01:00
* @ psalm - suppress MismatchingDocblockParamType
2017-06-13 20:00:41 +02:00
*/
2021-08-05 21:48:37 +02:00
function fooFoo ( array $_bar ) : void {
2017-06-13 20:00:41 +02:00
} ' ,
],
2017-07-25 22:11:02 +02:00
'differentDocblockParamClassSuppress' => [
' < ? php
class A {}
2018-05-21 06:46:56 +02:00
class B {}
2017-07-25 22:11:02 +02:00
/**
2021-08-05 21:48:37 +02:00
* @ param B $_bar
2018-01-05 18:11:12 +01:00
* @ psalm - suppress MismatchingDocblockParamType
2017-07-25 22:11:02 +02:00
*/
2021-08-05 21:48:37 +02:00
function fooFoo ( A $_bar ) : void {
2017-07-25 22:11:02 +02:00
} ' ,
],
'varDocblock' => [
' < ? php
/** @var array<Exception> */
$a = [];
2021-08-05 21:48:37 +02:00
echo $a [ 0 ] -> getMessage (); ' ,
2017-07-25 22:11:02 +02:00
],
2021-03-27 02:20:23 +01:00
'ignoreVarDocblock' => [
' < ? php
/**
* @ var array < Exception >
* @ ignore - var
*/
$a = [];
$a [ 0 ] -> getMessage (); ' ,
'assertions' => [],
'error_level' => [ 'EmptyArrayAccess' , 'MixedMethodCall' ],
],
'psalmIgnoreVarDocblock' => [
' < ? php
/**
* @ var array < Exception >
* @ psalm - ignore - var
*/
$a = [];
$a [ 0 ] -> getMessage (); ' ,
'assertions' => [],
'error_level' => [ 'EmptyArrayAccess' , 'MixedMethodCall' ],
],
2017-09-02 17:18:56 +02:00
'mixedDocblockParamTypeDefinedInParent' => [
' < ? php
class A {
/** @param mixed $a */
2018-01-11 21:50:45 +01:00
public function foo ( $a ) : void {}
2017-09-02 17:18:56 +02:00
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo ( $a ) : void {}
2017-09-02 17:18:56 +02:00
} ' ,
],
'intDocblockParamTypeDefinedInParent' => [
' < ? php
class A {
/** @param int $a */
2018-01-11 21:50:45 +01:00
public function foo ( $a ) : void {}
2017-09-02 17:18:56 +02:00
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo ( $a ) : void {}
2017-09-02 17:18:56 +02:00
} ' ,
],
2017-10-07 16:22:52 +02:00
'varSelf' => [
' < ? php
class A
{
2018-01-11 21:50:45 +01:00
public function foo () : void {}
2017-10-07 16:22:52 +02:00
2018-01-11 21:50:45 +01:00
public function getMeAgain () : void {
2017-10-07 16:22:52 +02:00
/** @var self */
$me = $this ;
$me -> foo ();
}
} ' ,
],
2017-11-03 02:45:17 +01:00
'psalmVar' => [
' < ? php
class A
{
/** @psalm-var array<int, string> */
public $foo = [];
2018-01-11 21:50:45 +01:00
public function updateFoo () : void {
2017-11-03 02:45:17 +01:00
$this -> foo [ 5 ] = " hello " ;
}
} ' ,
],
'psalmParam' => [
' < ? php
2021-08-05 21:48:37 +02:00
function takesInt ( int $_a ) : void {}
2017-11-03 02:45:17 +01:00
/**
* @ psalm - param array < int , string > $a
* @ param string [] $a
*/
2018-01-11 21:50:45 +01:00
function foo ( array $a ) : void {
2021-08-05 21:48:37 +02:00
foreach ( $a as $key => $_value ) {
2017-11-03 02:45:17 +01:00
takesInt ( $key );
}
} ' ,
],
2017-12-30 16:54:01 +01:00
'returnDocblock' => [
' < ? php
2018-01-11 21:50:45 +01:00
function foo ( int $i ) : int {
2017-12-30 16:54:01 +01:00
/** @var int */
return $i ;
} ' ,
],
2018-02-08 05:33:31 +01:00
'doubleVar' => [
' < ? php
function foo () : array {
return [ " hello " => new stdClass , " goodbye " => new stdClass ];
}
2021-08-05 21:48:37 +02:00
$_a = null ;
$_b = null ;
2018-02-08 05:33:31 +01:00
/**
2021-08-05 21:48:37 +02:00
* @ var string $_key
* @ var stdClass $_value
2018-02-08 05:33:31 +01:00
*/
2021-08-05 21:48:37 +02:00
foreach ( foo () as $_key => $_value ) {
$_a = $_key ;
$_b = $_value ;
2018-02-08 05:33:31 +01:00
} ' ,
'assertions' => [
2021-08-05 21:48:37 +02:00
'$_a' => 'null|string' ,
'$_b' => 'null|stdClass' ,
2018-02-08 05:33:31 +01:00
],
],
2018-03-21 15:46:21 +01:00
'allowOptionalParamsToBeEmptyArray' => [
' < ? php
2021-08-05 21:48:37 +02:00
/** @param array{b?: int, c?: string} $_a */
function foo ( array $_a = []) : void {} ' ,
2018-03-21 15:46:21 +01:00
],
2018-03-30 23:46:12 +02:00
'allowEmptyVarAnnotation' => [
' < ? php
/**
2021-08-05 21:48:37 +02:00
* @ param $_x
2018-03-30 23:46:12 +02:00
*/
2021-08-05 21:48:37 +02:00
function example ( array $_x ) : void {} ' ,
2018-03-30 23:46:12 +02:00
],
2018-04-04 18:39:05 +02:00
'allowCapitalisedNamespacedString' => [
' < ? php
namespace Foo ;
/**
2021-08-05 21:48:37 +02:00
* @ param String $_x
2018-04-04 18:39:05 +02:00
*/
2021-08-05 21:48:37 +02:00
function example ( string $_x ) : void {} ' ,
2018-04-04 18:39:05 +02:00
],
2018-04-20 21:22:48 +02:00
'megaClosureAnnotationWithoutSpacing' => [
' < ? php
/** @var array{a:Closure():(array<mixed, mixed>|null), b?:Closure():array<mixed, mixed>, c?:Closure():array<mixed, mixed>, d?:Closure():array<mixed, mixed>, e?:Closure():(array{f:null|string, g:null|string, h:null|string, i:string, j:mixed, k:mixed, l:mixed, m:mixed, n:bool, o?:array{0:string}}|null), p?:Closure():(array{f:null|string, g:null|string, h:null|string, q:string, i:string, j:mixed, k:mixed, l:mixed, m:mixed, n:bool, o?:array{0:string}}|null), r?:Closure():(array<mixed, mixed>|null), s:array<mixed, mixed>} */
$arr = [];
2018-09-17 18:15:45 +02:00
$arr [ " a " ](); ' ,
2018-04-20 21:22:48 +02:00
],
'megaClosureAnnotationWithSpacing' => [
' < ? php
2019-06-16 18:39:07 +02:00
/**
* @ var array {
* a : Closure () : ( array < mixed , mixed >| null ),
* b ? : Closure () : array < mixed , mixed > ,
* c ? : Closure () : array < mixed , mixed > ,
* d ? : Closure () : array < mixed , mixed > ,
* e ? : Closure () : ( array {
* f : null | string ,
* g : null | string ,
* h : null | string ,
* i : string ,
* j : mixed ,
* k : mixed ,
* l : mixed ,
* m : mixed ,
* n : bool ,
* o ? : array { 0 : string }
* } | null ),
* p ? : Closure () : ( array {
* f : null | string ,
* g : null | string ,
* h : null | string ,
* q : string ,
* i : string ,
* j : mixed ,
* k : mixed ,
* l : mixed ,
* m : mixed ,
* n : bool ,
* o ? : array { 0 : string }
* } | null ),
* r ? : Closure () : ( array < mixed , mixed >| null ),
* s : array < mixed , mixed >
* }
*
* Some text
*/
2018-04-20 21:22:48 +02:00
$arr = [];
2018-09-17 18:15:45 +02:00
$arr [ " a " ](); ' ,
2018-04-20 21:22:48 +02:00
],
2019-06-16 18:39:07 +02:00
'multipeLineGenericArray' => [
' < ? php
/**
* @ psalm - type MiddlewareArray = array <
* class - string < \Exception > ,
* array < int , string >
* >
*
* @ psalm - type RuleArray = array {
* rule : string ,
* controller ? : class - string < \Exception > ,
* redirect ? : string ,
* code ? : int ,
* type ? : string ,
* middleware ? : MiddlewareArray
* }
*
* Foo Bar
*/
class A {} ' ,
],
2019-11-19 01:01:12 +01:00
'builtInClassInAShape' => [
' < ? php
2020-03-22 15:44:48 +01:00
/**
* @ return array { d : Exception }
* @ psalm - suppress InvalidReturnType
*/
function f () {} '
2019-11-19 01:01:12 +01:00
],
2018-05-21 18:55:44 +02:00
'slashAfter?' => [
' < ? php
namespace ns ;
2021-08-05 21:48:37 +02:00
/** @param ?\stdClass $_s */
function foo ( $_s ) : void {
2018-05-21 18:55:44 +02:00
}
foo ( null );
foo ( new \stdClass ); ' ,
],
2018-05-29 15:44:38 +02:00
'returnTypeShouldBeNullable' => [
' < ? php
/**
* @ return stdClass
*/
function foo () : ? stdClass {
return rand ( 0 , 1 ) ? new stdClass : null ;
}
$f = foo ();
2019-03-23 19:27:54 +01:00
if ( $f ) {} ' ,
2018-05-29 15:44:38 +02:00
],
2019-04-09 19:58:49 +02:00
'spreadOperatorAnnotation' => [
2018-06-09 03:18:49 +02:00
' < ? php
2021-08-05 21:48:37 +02:00
/** @param string[] $_s */
function foo ( string ... $_s ) : void {}
/** @param string ...$_s */
function bar ( string ... $_s ) : void {}
2019-04-09 19:58:49 +02:00
foo ( " hello " , " goodbye " );
bar ( " hello " , " goodbye " );
foo ( ... [ " hello " , " goodbye " ]);
bar ( ... [ " hello " , " goodbye " ]); ' ,
2018-06-09 03:18:49 +02:00
],
2019-04-09 20:23:48 +02:00
'spreadOperatorByRefAnnotation' => [
' < ? php
/** @param string &...$s */
function foo ( &... $s ) : void {}
/** @param string ...&$s */
function bar ( &... $s ) : void {}
/** @param string[] &$s */
function bat ( &... $s ) : void {}
$a = " hello " ;
$b = " goodbye " ;
$c = " hello again " ;
foo ( $a );
2019-04-09 20:29:09 +02:00
bar ( $b );
bat ( $c ); ' ,
2019-04-09 20:23:48 +02:00
'assertions' => [
'$a' => 'string' ,
'$b' => 'string' ,
'$c' => 'string' ,
],
],
2018-07-05 23:25:26 +02:00
'valueReturnType' => [
' < ? php
/**
* @ param " a " | " b " $_p
*/
function acceptsLiteral ( $_p ) : void {}
/**
* @ return " a " | " b "
*/
function returnsLiteral () : string {
return rand ( 0 , 1 ) ? " a " : " b " ;
}
2019-03-23 19:27:54 +01:00
acceptsLiteral ( returnsLiteral ()); ' ,
2018-07-05 23:25:26 +02:00
],
2018-07-15 23:23:17 +02:00
'typeAliasBeforeClass' => [
' < ? php
/**
* @ psalm - type CoolType = A | B | null
*/
class A {}
class B {}
/** @return CoolType */
function foo () {
if ( rand ( 0 , 1 )) {
return new A ();
}
if ( rand ( 0 , 1 )) {
return new B ();
}
return null ;
}
2021-08-05 21:48:37 +02:00
/** @param CoolType $_a **/
function bar ( $_a ) : void { }
2018-07-15 23:23:17 +02:00
2019-03-23 19:27:54 +01:00
bar ( foo ()); ' ,
2018-07-15 23:23:17 +02:00
],
'typeAliasBeforeFunction' => [
' < ? php
/**
2018-09-05 04:27:55 +02:00
* @ psalm - type A_OR_B = A | B
* @ psalm - type CoolType = A_OR_B | null
2018-07-15 23:23:17 +02:00
* @ return CoolType
*/
function foo () {
if ( rand ( 0 , 1 )) {
return new A ();
2018-07-22 04:24:33 +02:00
}
if ( rand ( 0 , 1 )) {
return new B ();
}
return null ;
}
class A {}
class B {}
2021-08-05 21:48:37 +02:00
/** @param CoolType $_a **/
function bar ( $_a ) : void { }
2018-07-22 04:24:33 +02:00
2019-03-23 19:27:54 +01:00
bar ( foo ()); ' ,
2018-07-22 04:24:33 +02:00
],
'typeAliasInSeparateBlockBeforeFunction' => [
' < ? php
/**
* @ psalm - type CoolType = A | B | null
*/
/**
* @ return CoolType
*/
function foo () {
if ( rand ( 0 , 1 )) {
return new A ();
2018-07-15 23:23:17 +02:00
}
if ( rand ( 0 , 1 )) {
return new B ();
}
return null ;
}
class A {}
class B {}
2021-08-05 21:48:37 +02:00
/** @param CoolType $_a **/
function bar ( $_a ) : void { }
2018-07-15 23:23:17 +02:00
2019-03-23 19:27:54 +01:00
bar ( foo ()); ' ,
2018-07-15 23:23:17 +02:00
],
'almostFreeStandingTypeAlias' => [
' < ? php
/**
* @ psalm - type CoolType = A | B | null
*/
// this breaks up the line
class A {}
class B {}
/** @return CoolType */
function foo () {
if ( rand ( 0 , 1 )) {
return new A ();
}
if ( rand ( 0 , 1 )) {
return new B ();
}
return null ;
}
2021-08-05 21:48:37 +02:00
/** @param CoolType $_a **/
function bar ( $_a ) : void { }
2018-07-15 23:23:17 +02:00
2019-03-23 19:27:54 +01:00
bar ( foo ()); ' ,
2018-07-15 23:23:17 +02:00
],
2018-09-24 19:08:23 +02:00
'typeAliasUsedTwice' => [
' < ? php
/** @psalm-type TA = array<int, string> */
class Bar {
public function foo () : void {
$bar =
/** @return TA */
function () {
return [ " hello " ];
};
/** @var array<int, TA> */
$bat = [ $bar (), $bar ()];
foreach ( $bat as $b ) {
echo $b [ 0 ];
}
}
}
/**
2020-03-22 15:44:48 +01:00
* @ psalm - type _A = array { elt : int }
* @ param _A $p
* @ return _A
*/
2018-09-24 19:08:23 +02:00
function f ( $p ) {
2021-08-04 22:07:04 +02:00
/**
* @ psalm - suppress UnnecessaryVarAnnotation
* @ var _A
*/
2018-09-24 19:08:23 +02:00
$r = $p ;
return $r ;
} ' ,
],
2018-08-21 04:11:01 +02:00
'listUnpackWithDocblock' => [
' < ? php
interface I {}
class A implements I {
public function bar () : void {}
}
/** @return I[] */
function foo () : array {
return [ new A ()];
}
/** @var A $a1 */
2021-08-05 21:48:37 +02:00
[ $a1 , $_a2 ] = foo ();
2018-08-21 04:11:01 +02:00
$a1 -> bar (); ' ,
],
2018-08-23 04:53:44 +02:00
'spaceInType' => [
' < ? php
/** @return string | null */
function foo ( string $s = null ) {
return $s ;
} ' ,
],
2018-11-01 22:03:08 +01:00
'missingReturnTypeWithBadDocblockIgnoreBoth' => [
' < ? php
/**
* @ return [ bad ]
*/
function fooBar () {
} ' ,
[],
[
2021-12-03 20:11:20 +01:00
'InvalidDocblock' => Config :: REPORT_INFO ,
'MissingReturnType' => Config :: REPORT_INFO ,
2019-03-23 19:27:54 +01:00
],
2018-11-01 22:03:08 +01:00
],
2019-01-18 06:56:24 +01:00
'objectWithPropertiesAnnotation' => [
' < ? php
/** @param object{foo:string} $o */
function foo ( object $o ) : string {
return $o -> foo ;
}
$s = new \stdClass ();
$s -> foo = " hello " ;
foo ( $s );
class A {
/** @var string */
public $foo = " hello " ;
}
foo ( new A ); ' ,
],
2019-01-19 17:31:51 +01:00
'refineTypeInNestedCall' => [
' < ? php
function foo ( array $arr ) : \Generator {
/** @var array<string, mixed> $arr */
foreach ( array_filter ( array_keys ( $arr ), function ( string $key ) : bool {
return strpos ( $key , " BAR " ) === 0 ;
}) as $envVar ) {
yield $envVar => [ getenv ( $envVar )];
}
2019-03-23 19:27:54 +01:00
} ' ,
2019-01-19 17:31:51 +01:00
],
'allowAnnotationOnServer' => [
' < ? php
function foo () : \Generator {
/** @var array<string, mixed> $_SERVER */
foreach ( array_filter ( array_keys ( $_SERVER ), function ( string $key ) : bool {
return strpos ( $key , " BAR " ) === 0 ;
}) as $envVar ) {
yield $envVar => [ getenv ( $envVar )];
}
2019-03-23 19:27:54 +01:00
} ' ,
2019-01-19 17:31:51 +01:00
],
2019-01-20 17:49:13 +01:00
'annotationOnForeachItems' => [
' < ? php
function foo ( array $arr ) : void {
$item = null ;
/** @var string $item */
foreach ( $arr as $item ) {}
if ( is_null ( $item )) {}
}
function bar ( array $arr ) : void {
$item = null ;
/** @var string $item */
foreach ( $arr as $item => $_ ) {}
if ( is_null ( $item )) {}
}
function bat ( array $arr ) : void {
$item = null ;
2020-05-03 05:37:59 +02:00
/**
* @ psalm - suppress MixedArrayAccess
* @ var string $item
*/
2019-01-20 17:49:13 +01:00
foreach ( $arr as list ( $item )) {}
if ( is_null ( $item )) {}
}
function baz ( array $arr ) : void {
$item = null ;
2020-05-03 05:37:59 +02:00
/**
* @ psalm - suppress MixedArrayAccess
* @ var string $item
*/
2019-01-20 17:49:13 +01:00
foreach ( $arr as list ( $item => $_ )) {}
if ( is_null ( $item )) {}
} ' ,
[],
[
2019-03-23 19:27:54 +01:00
'MixedAssignment' ,
],
2019-01-20 17:49:13 +01:00
],
2019-02-23 17:39:00 +01:00
'extraneousDocblockParamName' => [
' < ? php
/**
2021-08-05 21:48:37 +02:00
* @ param string $_foo
2019-02-23 17:39:00 +01:00
* @ param string [] $bar
2021-08-05 21:48:37 +02:00
* @ param string [] $_barb
2019-02-23 17:39:00 +01:00
*/
2021-08-05 21:48:37 +02:00
function f ( string $_foo , array $_barb ) : void {} ' ,
2019-02-23 17:39:00 +01:00
],
2019-02-27 15:08:27 +01:00
'nonEmptyArray' => [
' < ? php
/** @param non-empty-array<string> $arr */
function foo ( array $arr ) : void {
foreach ( $arr as $a ) {}
echo $a ;
}
foo ([ " a " , " b " , " c " ]);
2019-03-02 14:35:50 +01:00
/** @param array<string> $arr */
function bar ( array $arr ) : void {
if ( ! $arr ) {
return ;
}
foo ( $arr );
2019-03-23 19:27:54 +01:00
} ' ,
2019-03-02 14:35:50 +01:00
],
'nonEmptyArrayInNamespace' => [
' < ? php
namespace ns ;
/** @param non-empty-array<string> $arr */
function foo ( array $arr ) : void {
foreach ( $arr as $a ) {}
echo $a ;
}
foo ([ " a " , " b " , " c " ]);
2019-02-27 15:08:27 +01:00
/** @param array<string> $arr */
function bar ( array $arr ) : void {
if ( ! $arr ) {
return ;
}
foo ( $arr );
2019-03-23 19:27:54 +01:00
} ' ,
2019-02-27 15:08:27 +01:00
],
2019-05-16 04:30:35 +02:00
'noExceptionOnIntersection' => [
' < ? php
class Foo {
/** @var null|\DateTime&\DateTimeImmutable */
private $s = null ;
} ' ,
],
2019-05-16 19:52:58 +02:00
'intersectionWithSpace' => [
' < ? php
interface A {
public function foo () : void ;
}
interface B {
public function bar () : void ;
}
/** @param A & B $a */
function f ( A $a ) : void {
$a -> foo ();
$a -> bar ();
2019-07-05 22:24:00 +02:00
} ' ,
2019-05-16 19:52:58 +02:00
],
2019-06-18 23:47:06 +02:00
'allowClosingComma' => [
' < ? php
2020-03-05 18:31:58 +01:00
/**
* @ psalm - type _Alias = array {
* foo : string ,
* bar : string ,
* baz : array {
* a : int ,
* },
* }
*/
class Foo { }
2019-06-18 23:47:06 +02:00
/**
* @ param array {
* foo : string ,
* bar : string ,
* baz : array {
* a : int ,
* },
* } $foo
*/
function foo ( array $foo ) : int {
2020-03-22 15:44:48 +01:00
return count ( $foo );
2019-06-18 23:47:06 +02:00
}
/**
* @ var array {
* foo : string ,
* bar : string ,
* baz : string ,
2021-08-05 21:48:37 +02:00
* } $_foo
2019-06-18 23:47:06 +02:00
*/
2021-08-05 21:48:37 +02:00
$_foo = [ " foo " => " " , " bar " => " " , " baz " => " " ]; ' ,
2019-06-18 23:47:06 +02:00
],
2019-11-21 17:03:18 +01:00
'returnNumber' => [
' < ? php
class C {
/**
* @ return 1
*/
public static function barBar () {
return 1 ;
}
} ' ,
],
'returnNumberForInterface' => [
' < ? php
interface I {
/**
* @ return 1
*/
public static function barBar ();
} ' ,
],
2019-12-11 16:30:40 +01:00
'psalmTypeAnnotationAboveReturn' => [
' < ? php
/**
* @ psalm - type Person = array { name : string , age : int }
*/
/**
* @ psalm - return Person
*/
function getPerson_error () : array {
$json = \ ' { " name " : " John " , " age " : 44 } \ ' ;
/** @psalm-var Person */
return json_decode ( $json , true );
} '
],
2020-08-30 17:44:14 +02:00
'allowDocblockDefinedTKeyedArrayIntoNonEmpty' => [
2020-01-14 03:02:47 +01:00
' < ? php
/** @param non-empty-array $_bar */
function foo ( array $_bar ) : void { }
/** @var array{0:list<string>, 1:list<int>} */
$bar = [[], []];
foo ( $bar ); '
],
2020-01-15 04:46:19 +01:00
'allowResourceInList' => [
' < ? php
/** @param list<scalar|array|object|resource|null> $_s */
function foo ( array $_s ) : void { } '
],
2020-01-23 20:52:35 +01:00
'possiblyUndefinedObjectProperty' => [
' < ? php
function consume ( string $value ) : void {
2020-03-22 15:44:48 +01:00
echo $value ;
2020-01-23 20:52:35 +01:00
}
/** @var object{value?: string} $data */
$data = json_decode ( " { } " , false );
consume ( $data -> value ? ? " " ); '
],
2020-01-31 00:00:31 +01:00
'throwSelf' => [
' < ? php
2020-02-13 19:01:53 +01:00
namespace Foo ;
2020-01-31 00:00:31 +01:00
class MyException extends \Exception {
/**
* @ throws self
*/
2020-03-22 15:44:48 +01:00
public static function create () : void {
throw new self ();
2020-01-31 00:00:31 +01:00
}
} '
],
2020-02-12 15:18:43 +01:00
'parseTrailingCommaInReturn' => [
' < ? php
/**
* @ psalm - return array {
* a : int ,
* b : string ,
* }
*/
function foo () : array {
return [ " a " => 1 , " b " => " two " ];
} '
],
2020-03-09 16:35:02 +01:00
'falsableFunctionAllowedWhenBooleanExpected' => [
' < ? php
/** @psalm-return bool */
function alwaysFalse1 ()
{
return false ;
}
function alwaysFalse2 () : bool
{
return false ;
} '
],
2020-04-20 15:22:58 +02:00
'dontInheritDocblockReturnWhenRedeclared' => [
' < ? php
interface Id {}
class UserId implements Id {}
interface Entity {
/** @psalm-return Id */
function id () : Id ;
}
class User implements Entity {
public function id () : UserId {
return new UserId ();
}
} ' ,
[],
[],
'7.4'
],
2020-04-26 23:36:02 +02:00
'arrayWithKeySlashesAndNewline' => [
' < ? php
2021-08-05 21:48:37 +02:00
$_arr = [ " foo \\ bar \n baz " => " literal " ]; ' ,
2020-04-26 23:36:02 +02:00
[
2021-08-05 21:48:37 +02:00
'$_arr' => 'array{\'foo\\\\bar\nbaz\': string}'
2020-04-26 23:36:02 +02:00
]
],
2020-05-03 01:39:10 +02:00
'doubleSpaceBeforeAt' => [
' < ? php
/**
2021-08-05 21:48:37 +02:00
* @ param string $_c
2020-05-03 01:39:10 +02:00
*/
2021-08-05 21:48:37 +02:00
function foo ( $_c ) : void {} '
2020-05-03 01:39:10 +02:00
],
2020-06-01 20:56:27 +02:00
'throwsAnnotationWithBarAndSpace' => [
' < ? php
/**
* @ throws \Exception | \InvalidArgumentException
*/
function bar () : void {} '
],
2020-07-26 19:23:21 +02:00
'varDocblockAboveCall' => [
' < ? php
function example ( string $s ) : void {
if ( preg_match ( \ ' { foo - ( \w + )} \ ' , $s , $m )) {
/** @var array{string, string} $m */
takesString ( $m [ 1 ]);
}
}
2021-08-05 21:48:37 +02:00
function takesString ( string $_s ) : void {} '
2020-07-26 19:23:21 +02:00
],
2020-09-01 06:12:12 +02:00
'noCrashWithoutAssignment' => [
' < ? php
/** @var DateTime $obj */
echo $obj -> format ( " Y " ); '
],
2020-11-02 06:40:36 +01:00
'intMaskWithClassConstants' => [
' < ? php
class FileFlag {
public const OPEN = 1 ;
public const MODIFIED = 2 ;
public const NEW = 4 ;
}
/**
* @ param int - mask < FileFlag :: OPEN , FileFlag :: MODIFIED , FileFlag :: NEW > $flags
*/
function takesFlags ( int $flags ) : void {
echo $flags ;
}
takesFlags ( FileFlag :: MODIFIED | FileFlag :: NEW ); '
],
2021-07-16 22:20:33 +02:00
'intMaskWithZero' => [
' < ? php
/** @param int-mask<1,2> $_flags */
function takesFlags ( int $_flags ) : void {}
takesFlags ( 0 );
'
],
2020-11-02 06:40:36 +01:00
'intMaskOfWithClassWildcard' => [
' < ? php
class FileFlag {
public const OPEN = 1 ;
public const MODIFIED = 2 ;
public const NEW = 4 ;
}
/**
* @ param int - mask - of < FileFlag ::*> $flags
*/
function takesFlags ( int $flags ) : void {
echo $flags ;
}
takesFlags ( FileFlag :: MODIFIED | FileFlag :: NEW ); '
],
2021-07-16 22:20:33 +02:00
'intMaskOfWithZero' => [
' < ? php
class FileFlag {
public const OPEN = 1 ;
public const MODIFIED = 2 ;
public const NEW = 4 ;
}
/** @param int-mask-of<FileFlag::*> $_flags */
function takesFlags ( int $_flags ) : void {}
takesFlags ( 0 );
'
],
2021-05-22 18:36:02 +02:00
'emptyStringFirst' => [
' < ? php
/**
* @ param \ ' \ ' | \ ' a\ ' | \ ' b\ ' $v
*/
function testBad ( string $v ) : void {
echo $v ;
} '
],
2021-08-04 22:07:04 +02:00
'UnnecessaryVarAnnotationSuppress' => [
' < ? php
/** @psalm-consistent-constructor */
final class Foo {}
/**
* @ param class - string $class
*/
function foo ( string $class ) : Foo {
if ( ! is_subclass_of ( $class , Foo :: class )) {
throw new \LogicException ();
}
/**
* @ psalm - suppress UnnecessaryVarAnnotation
* @ var Foo $instance
*/
$instance = new $class ();
return $instance ;
} ' ,
],
2021-08-06 22:00:37 +02:00
'suppressNonInvariantDocblockPropertyType' => [
' < ? php
class Vendor
{
/**
* @ var array
*/
2021-08-07 10:18:06 +02:00
public array $config = [];
2021-08-06 22:00:37 +02:00
public function getConfig () : array { return $this -> config ;}
}
class A extends Vendor
{
/**
* @ var string []
* @ psalm - suppress NonInvariantDocblockPropertyType
*/
2021-08-07 10:18:06 +02:00
public array $config = [];
2021-08-06 22:00:37 +02:00
}
$a = new Vendor ();
$_b = new A ();
echo ( string )( $a -> getConfig ()[ 0 ] ? ? " " ); '
],
2021-10-27 22:15:57 +02:00
'promotedPropertiesDocumentationEitherForParamOrForProperty' => [
' < ? php
final class UserRole
{
/** @psalm-param stdClass $id */
public function __construct (
protected $id ,
/** @psalm-var stdClass */
protected $id2
) {
}
}
new UserRole ( new stdClass (), new stdClass ());
'
],
2021-11-09 19:38:18 +01:00
'promotedPropertiesDocumentationForPropertyAndSignature' => [
' < ? php
final class A
{
public function __construct (
/**
* @ var iterable < string >
*/
private iterable $strings ,
) {
}
} '
],
2017-04-25 05:45:02 +02:00
];
}
/**
2021-03-20 02:44:44 +01:00
* @ return iterable < string , array { string , error_message : string , 1 ? : string [], 2 ? : bool , 3 ? : string } >
2017-04-25 05:45:02 +02:00
*/
2020-09-12 17:24:05 +02:00
public function providerInvalidCodeParse () : iterable
2017-04-25 05:45:02 +02:00
{
return [
2019-05-17 00:36:36 +02:00
'invalidClassMethodReturn' => [
' < ? php
class C {
/**
* @ return $thus
*/
public function barBar () {
return $this ;
}
} ' ,
'error_message' => 'MissingDocblockType' ,
],
2019-11-21 17:03:18 +01:00
2019-05-17 00:36:36 +02:00
'invalidClassMethodReturnBrackets' => [
' < ? php
class C {
/**
* @ return []
*/
public static function barBar () {
return [];
}
} ' ,
'error_message' => 'InvalidDocblock' ,
],
'invalidInterfaceMethodReturn' => [
2017-05-25 03:11:18 +02:00
' < ? php
interface I {
/**
* @ return $thus
*/
public static function barBar ();
} ' ,
2017-11-15 03:43:31 +01:00
'error_message' => 'MissingDocblockType' ,
2017-05-25 03:11:18 +02:00
],
2019-05-17 00:36:36 +02:00
'invalidInterfaceMethodReturnBrackets' => [
2018-03-25 03:02:44 +02:00
' < ? php
interface I {
/**
* @ return []
*/
public static function barBar ();
} ' ,
'error_message' => 'InvalidDocblock' ,
],
'invalidPropertyBrackets' => [
' < ? php
class A {
/**
* @ var []
*/
public $bar ;
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2017-07-10 02:32:35 +02:00
'invalidReturnClassWithComma' => [
' < ? php
interface I {
/**
* @ return 1 ,
*/
public static function barBar ();
} ' ,
'error_message' => 'InvalidDocblock' ,
],
'returnClassWithComma' => [
' < ? php
interface I {
/**
* @ return a ,
*/
public static function barBar ();
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2017-04-25 05:45:02 +02:00
'missingParamType' => [
' < ? php
/**
2017-07-25 22:11:02 +02:00
* @ param string $bar
2017-04-25 05:45:02 +02:00
*/
2018-01-11 21:50:45 +01:00
function fooBar () : void {
2017-07-25 22:11:02 +02:00
}
fooBar ( " hello " ); ' ,
2020-03-15 18:44:00 +01:00
'error_message' => 'TooManyArguments - src' . DIRECTORY_SEPARATOR . 'somefile.php:8:21 - Too many arguments for fooBar '
2018-02-02 17:26:55 +01:00
. '- expecting 0 but saw 1' ,
2017-04-25 05:45:02 +02:00
],
'missingParamVar' => [
' < ? php
/**
* @ param string
*/
2018-01-11 21:50:45 +01:00
function fooBar () : void {
2017-04-25 05:45:02 +02:00
} ' ,
2019-02-27 22:00:44 +01:00
'error_message' => 'InvalidDocblock - src' . DIRECTORY_SEPARATOR . 'somefile.php:5:21 - Badly-formatted @param' ,
2017-04-25 05:45:02 +02:00
],
2019-08-01 22:10:12 +02:00
'invalidSlashWithString' => [
' < ? php
/**
* @ return \ ? string
*/
function foo () {
2020-03-22 15:44:48 +01:00
return rand ( 0 , 1 ) ? " hello " : null ;
2019-08-01 22:10:12 +02:00
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-11-01 22:03:08 +01:00
'missingReturnTypeWithBadDocblock' => [
' < ? php
/**
* @ return [ bad ]
*/
function fooBar () {
} ' ,
'error_message' => 'MissingReturnType' ,
[
2021-12-03 20:11:20 +01:00
'InvalidDocblock' => Config :: REPORT_INFO ,
2019-03-23 19:27:54 +01:00
],
2018-11-01 22:03:08 +01:00
],
2017-04-25 05:45:02 +02:00
'invalidDocblockReturn' => [
' < ? php
/**
* @ return string
*/
2018-01-11 21:50:45 +01:00
function fooFoo () : int {
2017-07-25 22:11:02 +02:00
return 5 ;
2017-04-25 05:45:02 +02:00
} ' ,
2018-01-05 18:11:12 +01:00
'error_message' => 'MismatchingDocblockReturnType' ,
2017-05-05 00:35:05 +02:00
],
2017-09-02 17:18:56 +02:00
'intParamTypeDefinedInParent' => [
' < ? php
class A {
2018-01-11 21:50:45 +01:00
public function foo ( int $a ) : void {}
2017-09-02 17:18:56 +02:00
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo ( $a ) : void {}
2017-09-02 17:18:56 +02:00
} ' ,
2018-01-29 02:03:47 +01:00
'error_message' => 'MissingParamType' ,
2017-09-02 17:18:56 +02:00
'error_levels' => [ 'MethodSignatureMismatch' ],
],
2017-11-03 02:45:17 +01:00
'psalmInvalidVar' => [
' < ? php
class A
{
/** @psalm-var array<int, string> */
public $foo = [];
2018-01-11 21:50:45 +01:00
public function updateFoo () : void {
2017-11-03 02:45:17 +01:00
$this -> foo [ " boof " ] = " hello " ;
}
} ' ,
2018-01-11 23:38:24 +01:00
'error_message' => 'InvalidPropertyAssignmentValue' ,
2017-11-03 02:45:17 +01:00
],
2017-11-15 03:43:31 +01:00
'incorrectDocblockOrder' => [
' < ? php
class MyClass {
/**
* Comment
* @ var $fooPropTypo string
*/
public $fooProp = " /tmp/file.txt " ;
} ' ,
'error_message' => 'MissingDocblockType' ,
],
'badlyFormattedVar' => [
' < ? php
/**
* @ return string []
*/
function returns_strings () {
/** @var array(string) $result */
$result = [ " example " ];
return $result ;
} ' ,
'error_message' => 'InvalidDocblock' ,
],
'badlyWrittenVar' => [
' < ? php
/** @param mixed $x */
2018-01-11 21:50:45 +01:00
function myvalue ( $x ) : void {
2017-11-15 03:43:31 +01:00
/** @var $myVar MyNS\OtherClass */
$myVar = $x -> conn () -> method ();
$myVar -> otherMethod ();
} ' ,
'error_message' => 'MissingDocblockType' ,
],
2018-01-08 23:17:49 +01:00
'dontOverrideSameType' => [
' < ? php
class A {
/** @return ?int */
2018-01-11 21:50:45 +01:00
public function foo () : ? int {
2018-01-08 23:17:49 +01:00
if ( rand ( 0 , 1 )) return 5 ;
}
} ' ,
'error_message' => 'InvalidReturnType' ,
],
2018-01-10 04:46:55 +01:00
'alwaysCheckReturnType' => [
' < ? php
class A {}
/**
* @ return A
* @ psalm - suppress MismatchingDocblockReturnType
*/
2018-01-11 21:50:45 +01:00
function foo () : B {
2020-03-22 15:44:48 +01:00
return new A ;
2018-01-10 04:46:55 +01:00
} ' ,
'error_message' => 'UndefinedClass' ,
],
2018-01-10 06:07:47 +01:00
'preventBadBoolean' => [
' < ? php
2018-01-11 21:50:45 +01:00
function foo () : boolean {
2018-01-10 06:07:47 +01:00
return true ;
} ' ,
'error_message' => 'UndefinedClass' ,
],
2019-05-16 00:41:26 +02:00
'undefinedDocblockClassCall' => [
' < ? php
class B {
/**
* @ return A
* @ psalm - suppress UndefinedDocblockClass
* @ psalm - suppress InvalidReturnStatement
* @ psalm - suppress InvalidReturnType
*/
public function foo () {
return new stdClass ();
}
public function bar () {
$this -> foo () -> bar ();
}
}
' ,
'error_message' => 'UndefinedDocblockClass' ,
],
2020-08-30 17:44:14 +02:00
'preventBadTKeyedArrayFormat' => [
2018-01-19 22:06:30 +01:00
' < ? php
/**
* @ param array {} $arr
*/
function bar ( array $arr ) : void {} ' ,
'error_message' => 'InvalidDocblock' ,
2018-01-20 17:48:16 +01:00
],
2018-02-01 07:10:27 +01:00
'noPhpStormAnnotationsThankYou' => [
' < ? php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString ( ArrayIterator $i ) : void {} ' ,
'error_message' => 'MismatchingDocblockParamType' ,
],
'noPhpStormAnnotationsPossiblyInvalid' => [
' < ? php
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString ( $i ) : void {
$s = $i -> offsetGet ( " a " );
} ' ,
'error_message' => 'PossiblyInvalidMethodCall' ,
],
2018-03-29 08:20:19 +02:00
'doubleBar' => [
' < ? php
/** @param PDO||Closure|numeric $a */
function foo ( $a ) : void {} ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-04-05 20:11:57 +02:00
'badStringVar' => [
' < ? php
/** @var string; */
$a = " hello " ; ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-04-16 00:16:31 +02:00
'badCallableVar' => [
' < ? php
/** @return Closure(int): */
function foo () : callable {
2018-09-17 18:15:45 +02:00
return function () : void {};
2018-04-16 00:16:31 +02:00
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-04-25 03:27:31 +02:00
'hyphenInType' => [
' < ? php
/**
* @ return - Description
*/
function example () {
return " placeholder " ;
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-06-11 23:23:28 +02:00
'badAmpersand' => [
' < ? php
/** @return &array */
function foo () : array {
return [];
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2018-07-15 23:23:17 +02:00
'invalidTypeAlias' => [
' < ? php
/**
* @ psalm - type CoolType = A | B >
*/
class A {} ' ,
'error_message' => 'InvalidDocblock' ,
],
2020-08-30 17:44:14 +02:00
'typeAliasInTKeyedArray' => [
2018-08-09 04:44:02 +02:00
' < ? php
/**
* @ psalm - type aType null | " a " | " b " | " c " | " d "
*/
/** @psalm-return array{0:bool,1:aType} */
function f () : array {
return [( bool ) rand ( 0 , 1 ), rand ( 0 , 1 ) ? " z " : null ];
} ' ,
'error_message' => 'InvalidReturnStatement' ,
],
2019-01-08 20:50:45 +01:00
'noCrashOnHalfDoneArrayPropertyType' => [
' < ? php
class A {
/** @var array< */
private $foo = [];
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2020-08-30 17:44:14 +02:00
'noCrashOnHalfDoneTKeyedArrayPropertyType' => [
2019-01-08 20:50:45 +01:00
' < ? php
class A {
/** @var array{ */
private $foo = [];
} ' ,
'error_message' => 'InvalidDocblock' ,
],
2019-01-08 21:11:57 +01:00
'noCrashOnInvalidClassTemplateAsType' => [
' < ? php
/**
* @ template T as ' . '
*/
class A {} ' ,
'error_message' => 'InvalidDocblock' ,
],
'noCrashOnInvalidFunctionTemplateAsType' => [
' < ? php
/**
* @ template T as ' . '
*/
function foo () : void {} ' ,
'error_message' => 'InvalidDocblock' ,
],
2019-01-09 17:51:29 +01:00
'returnTypeNewLineIsIgnored' => [
' < ? php
/**
* @ return
* Some text
*/
function foo () {} ' ,
'error_message' => 'MissingReturnType' ,
],
2019-01-18 06:56:24 +01:00
'objectWithPropertiesAnnotationNoMatchingProperty' => [
' < ? php
/** @param object{foo:string} $o */
function foo ( object $o ) : string {
return $o -> foo ;
}
class A {}
foo ( new A ); ' ,
'error_message' => 'InvalidArgument' ,
],
2019-01-26 23:30:44 +01:00
'badVar' => [
' < ? php
/** @var Foo */
$a = $_GET [ " foo " ]; ' ,
2019-05-16 00:41:26 +02:00
'error_message' => 'UndefinedDocblockClass' ,
2019-01-26 23:30:44 +01:00
],
2019-02-08 00:10:32 +01:00
'badPsalmType' => [
' < ? php
/**
* @ psalm - type Foo = array { a : }
*/ ' ,
'error_message' => 'InvalidDocblock' ,
],
2019-02-23 17:02:04 +01:00
'mismatchingDocblockParamName' => [
' < ? php
2019-02-27 22:00:44 +01:00
/** @param string[] $bar */
function f ( array $barb ) : void {} ' ,
2019-02-28 20:46:40 +01:00
'error_message' => 'InvalidDocblockParamName - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:41' ,
2019-02-23 17:02:04 +01:00
],
2019-02-27 15:08:27 +01:00
'nonEmptyArrayCalledWithEmpty' => [
' < ? php
/** @param non-empty-array<string> $arr */
function foo ( array $arr ) : void {
foreach ( $arr as $a ) {}
echo $a ;
}
foo ([]); ' ,
'error_message' => 'InvalidArgument' ,
],
2019-03-02 14:35:50 +01:00
'nonEmptyArrayCalledWithEmptyInNamespace' => [
' < ? php
namespace ns ;
/** @param non-empty-array<string> $arr */
function foo ( array $arr ) : void {
foreach ( $arr as $a ) {}
echo $a ;
}
foo ([]); ' ,
'error_message' => 'InvalidArgument' ,
],
2019-02-27 15:08:27 +01:00
'nonEmptyArrayCalledWithArray' => [
' < ? php
/** @param non-empty-array<string> $arr */
function foo ( array $arr ) : void {
foreach ( $arr as $a ) {}
echo $a ;
}
/** @param array<string> $arr */
function bar ( array $arr ) {
foo ( $arr );
} ' ,
2019-04-26 00:02:19 +02:00
'error_message' => 'ArgumentTypeCoercion' ,
2019-02-27 15:08:27 +01:00
],
2019-04-09 19:58:49 +02:00
'spreadOperatorArrayAnnotationBadArg' => [
' < ? php
2021-08-05 21:32:32 +02:00
/** @param string[] $_s */
function foo ( string ... $_s ) : void {}
2019-04-09 19:58:49 +02:00
foo ( 5 ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
'spreadOperatorArrayAnnotationBadSpreadArg' => [
' < ? php
2021-08-05 21:32:32 +02:00
/** @param string[] $_s */
function foo ( string ... $_s ) : void {}
2019-04-09 19:58:49 +02:00
foo ( ... [ 5 ]); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2019-04-09 20:29:09 +02:00
'spreadOperatorByRefAnnotationBadCall1' => [
' < ? php
/** @param string &...$s */
function foo ( &... $s ) : void {}
$a = 1 ;
foo ( $a ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
'spreadOperatorByRefAnnotationBadCall2' => [
' < ? php
/** @param string ...&$s */
function foo ( &... $s ) : void {}
$b = 2 ;
foo ( $b ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
'spreadOperatorByRefAnnotationBadCall3' => [
' < ? php
/** @param string[] &$s */
function foo ( &... $s ) : void {}
$c = 3 ;
foo ( $c ); ' ,
'error_message' => 'InvalidScalarArgument' ,
],
2019-06-01 22:57:33 +02:00
'identifyReturnType' => [
' < ? php
/** @return array{hello: string} */
function foo () {} ' ,
'error_message' => 'InvalidReturnType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2:33' ,
],
2019-06-03 21:46:25 +02:00
'invalidParamDocblockAsterisk' => [
' < ? php
/**
* @ param * $reference
*/
function f ( $reference ) {} ' ,
2019-07-05 22:24:00 +02:00
'error_message' => 'MissingDocblockType' ,
2019-06-26 21:11:16 +02:00
],
2020-03-09 16:35:02 +01:00
'canNeverReturnDeclaredType' => [
' < ? php
/** @psalm-return false */
function alwaysFalse () : bool
{
return true ;
} ' ,
'error_message' => 'InvalidReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:6:32' ,
],
'falsableWithExpectedTypeTrue' => [
' < ? php
/** @psalm-return true */
function alwaysFalse ()
{
return false ;
} ' ,
'error_message' => 'FalsableReturnStatement - src' . DIRECTORY_SEPARATOR . 'somefile.php:6:32' ,
],
2020-05-30 22:54:16 +02:00
'DuplicatedParam' => [
' < ? php
/**
* @ psalm - param array $arr
* @ psalm - param array $arr
*/
function bar ( array $arr ) : void {} ' ,
'error_message' => 'InvalidDocblock - src' . DIRECTORY_SEPARATOR . 'somefile.php:6:21 - Found duplicated @param or prefixed @param tag in docblock for bar' ,
],
'DuplicatedReturn' => [
' < ? php
/**
* @ return void
* @ return void
*/
function bar ( array $arr ) : void {} ' ,
'error_message' => 'InvalidDocblock - src' . DIRECTORY_SEPARATOR . 'somefile.php:6:21 - Found duplicated @return or prefixed @return tag in docblock for bar' ,
],
2020-08-30 17:44:14 +02:00
'missingClassForTKeyedArray' => [
2020-07-24 15:38:51 +02:00
' < ? php
interface I {
/** @return object{id: int, a: int} */
public function run ();
}
class C implements I {
/** @return X */
public function run () {}
} ' ,
'error_message' => 'ImplementedReturnTypeMismatch'
],
2021-05-28 15:47:39 +02:00
'unexpectedImportType' => [
' < ? php
/** @psalm-import-type asd */
function f () : void {}
' ,
'error_message' => 'PossiblyInvalidDocblockTag' ,
],
'unexpectedVarOnFunction' => [
' < ? php
/** @var int $p */
function f ( $p ) : void {}
' ,
'error_message' => 'PossiblyInvalidDocblockTag' ,
],
2021-07-25 11:42:40 +02:00
'unterminatedParentheses' => [
' < ? php
/** @return ( */
function f () {}
' ,
'error_message' => 'InvalidDocblock' ,
],
'emptyParentheses' => [
' < ? php
/** @return () */
function f () {}
' ,
'error_message' => 'InvalidDocblock' ,
],
'unbalancedParentheses' => [
" <?php
/** @return ((string) */
function f () : string {
return '' ;
}
" ,
'error_message' => 'InvalidDocblock' ,
],
2021-10-27 22:45:16 +02:00
'promotedPropertiesDocumentationFailsWhenSendingBadTypeAgainstParam' => [
2021-10-27 22:15:57 +02:00
' < ? php
final class UserRole
{
/** @psalm-param stdClass $id */
public function __construct (
protected $id
) {
}
}
new UserRole ( " a " );
' ,
'error_message' => 'InvalidArgument' ,
],
2021-10-27 22:45:16 +02:00
'promotedPropertiesDocumentationFailsWhenSendingBadTypeAgainstProperty' => [
2021-10-27 22:15:57 +02:00
' < ? php
final class UserRole
{
public function __construct (
/** @psalm-var stdClass */
protected $id2
) {
}
}
new UserRole ( " a " );
' ,
'error_message' => 'InvalidArgument' ,
],
2021-10-28 22:05:43 +02:00
'promotedPropertyDuplicateDoc' => [
' < ? php
final class UserRole
{
/** @psalm-param string $id */
public function __construct (
/** @psalm-var stdClass */
protected $id
) {
}
}
' ,
'error_message' => 'InvalidDocblock' ,
],
2021-11-28 11:10:55 +01:00
'promotedPropertyWithParamDocblockAndSignatureType' => [
' < ? php
class A
{
public function __construct (
/** @var "cti"|"basic"|"teams"|"" */
public string $licenseType = " " ,
) {
}
}
2021-10-28 22:05:43 +02:00
2021-11-28 11:10:55 +01:00
$a = new A ( " ladida " );
$a -> licenseType = " dudidu " ;
2021-10-28 22:05:43 +02:00
2021-11-28 11:10:55 +01:00
echo $a -> licenseType ; ' ,
'error_message' => 'InvalidArgument' ,
],
2017-04-25 05:45:02 +02:00
];
2016-12-31 06:14:00 +01:00
}
2016-12-12 05:41:11 +01:00
}