1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00
psalm/tests/AnnotationTest.php

1473 lines
49 KiB
PHP
Raw Normal View History

2016-12-12 05:41:11 +01:00
<?php
namespace Psalm\Tests;
use Psalm\Config;
use Psalm\Context;
class AnnotationTest extends TestCase
2016-12-12 05:41:11 +01:00
{
use Traits\FileCheckerInvalidCodeParseTestTrait;
use Traits\FileCheckerValidCodeParseTestTrait;
/**
* @return void
*/
public function testPhpStormGenericsWithValidArgument()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
function takesString(string $s): void {}
/** @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());
}
2018-03-17 20:40:57 +01:00
/**
* @return void
*/
public function testPhpStormGenericsWithClassProperty()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
/** @psalm-suppress MissingConstructor */
class Foo {
/** @var \stdClass[]|\ArrayObject */
public $bar;
/**
* @return \stdClass[]|\ArrayObject
*/
public function getBar(): \ArrayObject
{
return $this->bar;
}
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @return void
*/
public function testPhpStormGenericsWithValidIterableArgument()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
function takesString(string $s): void {}
/** @param iterable|string[] $i */
function takesArrayIteratorOfString(iterable $i): void {
foreach ($i as $s2) {
takesString($s2);
}
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidScalarArgument
*
* @return void
*/
public function testPhpStormGenericsInvalidArgument()
{
Config::getInstance()->allow_phpstorm_generics = true;
$this->addFile(
'somefile.php',
'<?php
function takesInt(int $s): void {}
/** @param ArrayIterator|string[] $i */
function takesArrayIteratorOfString(ArrayIterator $i): void {
$s = $i->offsetGet("a");
takesInt($s);
}'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage PossiblyInvalidMethodCall
*
* @return void
*/
public function testPhpStormGenericsNoTypehint()
{
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());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidArgument
*
* @return void
*/
public function testDontAllowStringConstCoercion()
{
Config::getInstance()->allow_coercion_from_string_to_class_const = false;
$this->addFile(
'somefile.php',
'<?php
/**
* @param class-string $s
*/
function takesClassConstants(string $s) : void {}
class A {}
takesClassConstants("A");'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidStringClass
*
* @return void
*/
public function testDontAllowStringStandInForNewClass()
{
Config::getInstance()->allow_string_standin_for_class = false;
$this->addFile(
'somefile.php',
'<?php
class A {}
$a = "A";
new $a();'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @expectedException \Psalm\Exception\CodeException
* @expectedExceptionMessage InvalidStringClass
*
* @return void
*/
public function testDontAllowStringStandInForStaticMethodCall()
{
Config::getInstance()->allow_string_standin_for_class = false;
$this->addFile(
'somefile.php',
'<?php
class A {
public static function foo() : void {}
}
$a = "A";
$a::foo();'
);
$this->analyzeFile('somefile.php', new Context());
}
/**
* @return array
*/
public function providerFileCheckerValidCodeParse()
{
return [
'nopType' => [
'<?php
$a = "hello";
/** @var int $a */',
'assertions' => [
'$a' => 'int',
],
],
'deprecatedMethod' => [
'<?php
class Foo {
/**
* @deprecated
*/
2018-01-11 21:50:45 +01:00
public static function barBar(): void {
}
2017-05-27 02:05:57 +02:00
}',
],
'validDocblockReturn' => [
'<?php
/**
* @return string
*/
2018-01-11 21:50:45 +01:00
function fooFoo(): string {
return "boop";
}
/**
* @return array<int, string>
*/
2018-01-11 21:50:45 +01:00
function foo2(): array {
return ["hello"];
}
/**
* @return array<int, string>
*/
2018-01-11 21:50:45 +01:00
function foo3(): array {
return ["hello"];
2017-05-27 02:05:57 +02:00
}',
],
'reassertWithIs' => [
'<?php
/** @param array $a */
2018-01-11 21:50:45 +01:00
function foo($a): void {
if (is_array($a)) {
// do something
}
2017-05-27 02:05:57 +02:00
}',
'assertions' => [],
'error_level' => ['RedundantConditionGivenDocblockType'],
],
'checkArrayWithIs' => [
'<?php
/** @param mixed $b */
2018-01-11 21:50:45 +01:00
function foo($b): void {
/** @var array */
$a = (array)$b;
if (is_array($a)) {
// do something
}
2017-05-27 02:05:57 +02:00
}',
'assertions' => [],
'error_level' => ['RedundantConditionGivenDocblockType'],
],
'checkArrayWithIsInsideLoop' => [
'<?php
/** @param array<mixed, array<mixed, mixed>> $data */
2018-01-11 21:50:45 +01:00
function foo($data): void {
foreach ($data as $key => $val) {
if (!\is_array($data)) {
$data = [$key => null];
} else {
$data[$key] = !empty($val);
}
}
2017-05-27 02:05:57 +02:00
}',
'assertions' => [],
'error_level' => ['LoopInvalidation', 'MixedArrayOffset', 'RedundantConditionGivenDocblockType'],
],
'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-05-27 02:05:57 +02:00
}',
],
'goodDocblockInNamespace' => [
'<?php
namespace Foo;
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-05-27 02:05:57 +02:00
}',
],
'propertyDocblock' => [
'<?php
2018-02-04 05:10:22 +01:00
namespace Bar;
/**
* @property string $foo
*/
class A {
/** @param string $name */
2018-01-11 21:50:45 +01:00
public function __get($name): ?string {
if ($name === "foo") {
return "hello";
}
}
/**
* @param string $name
* @param mixed $value
*/
2018-01-11 21:50:45 +01:00
public function __set($name, $value): void {
}
}
$a = new A();
$a->foo = "hello";
$a->bar = "hello"; // not a property',
],
2018-02-04 05:10:22 +01:00
'propertyOfTypeClassDocblock' => [
'<?php
namespace Bar;
class PropertyType {}
/**
* @property PropertyType $foo
*/
class A {
/** @param string $name */
public function __get($name): ?string {
if ($name === "foo") {
return "hello";
}
}
/**
* @param string $name
* @param mixed $value
*/
public function __set($name, $value): void {
}
}
$a = new A();
$a->foo = new PropertyType();',
],
'propertySealedDocblockDefinedPropertyFetch' => [
'<?php
2018-02-04 05:10:22 +01:00
namespace Bar;
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
echo $a->foo;',
],
'ignoreNullableReturn' => [
'<?php
class A {
/** @var int */
public $bar = 5;
2018-01-11 21:50:45 +01:00
public function foo(): void {}
}
/**
* @return ?A
* @psalm-ignore-nullable-return
*/
function makeA() {
2018-01-11 21:50:45 +01:00
return rand(0, 1) ? new A(): null;
}
2018-01-11 21:50:45 +01:00
function takeA(A $a): void { }
$a = makeA();
$a->foo();
$a->bar = 7;
2017-05-27 02:05:57 +02:00
takeA($a);',
],
'invalidDocblockParamSuppress' => [
'<?php
/**
* @param int $bar
* @psalm-suppress MismatchingDocblockParamType
*/
2018-01-11 21:50:45 +01:00
function fooFoo(array $bar): void {
}',
],
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
'differentDocblockParamClassSuppress' => [
'<?php
class A {}
/**
* @param B $bar
* @psalm-suppress MismatchingDocblockParamType
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
*/
2018-01-11 21:50:45 +01:00
function fooFoo(A $bar): void {
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
}',
],
'varDocblock' => [
'<?php
/** @var array<Exception> */
$a = [];
$a[0]->getMessage();',
],
'mixedDocblockParamTypeDefinedInParent' => [
'<?php
class A {
/** @param mixed $a */
2018-01-11 21:50:45 +01:00
public function foo($a): void {}
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo($a): void {}
}',
],
'intDocblockParamTypeDefinedInParent' => [
'<?php
class A {
/** @param int $a */
2018-01-11 21:50:45 +01:00
public function foo($a): void {}
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo($a): void {}
}',
],
'varSelf' => [
'<?php
class A
{
2018-01-11 21:50:45 +01:00
public function foo(): void {}
2018-01-11 21:50:45 +01:00
public function getMeAgain(): void {
/** @var self */
$me = $this;
$me->foo();
}
}',
],
'psalmVar' => [
'<?php
class A
{
/** @psalm-var array<int, string> */
public $foo = [];
2018-01-11 21:50:45 +01:00
public function updateFoo(): void {
$this->foo[5] = "hello";
}
}',
],
'psalmParam' => [
'<?php
2018-01-11 21:50:45 +01:00
function takesInt(int $a): void {}
/**
* @psalm-param array<int, string> $a
* @param string[] $a
*/
2018-01-11 21:50:45 +01:00
function foo(array $a): void {
foreach ($a as $key => $value) {
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;
}',
],
'doubleVar' => [
'<?php
function foo() : array {
return ["hello" => new stdClass, "goodbye" => new stdClass];
}
$a = null;
$b = null;
/**
* @var string $key
* @var stdClass $value
*/
foreach (foo() as $key => $value) {
$a = $key;
$b = $value;
}',
'assertions' => [
'$a' => 'null|string',
'$b' => 'null|stdClass',
],
],
/**
* With a magic setter and no annotations specifying properties or types, we can
* set anything we want on any variable name. The magic setter is trusted to figure
* it out.
*/
'magicSetterUndefinedPropertyNoAnnotation' => [
'<?php
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function goodSet(): void {
$this->__set("foo", new stdClass());
}
}',
],
/**
* With a magic getter and no annotations specifying properties or types, we can
* get anything we want with any variable name. The magic getter is trusted to figure
* it out.
*/
'magicGetterUndefinedPropertyNoAnnotation' => [
'<?php
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function goodGet(): void {
echo $this->__get("foo");
}
}',
],
/**
* The property $foo is defined as a string with the `@property` annotation. We
* use the magic setter to set it to a string, so everything is cool.
*/
'magicSetterValidAssignmentType' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function goodSet(): void {
$this->__set("foo", "value");
}
}',
],
'propertyDocblockAssignmentToMixed' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
}
/** @param mixed $b */
function foo($b) : void {
$a = new A();
$a->__set("foo", $b);
}',
'assertions' => [],
'error_level' => ['MixedAssignment', 'MixedTypeCoercion'],
],
'arrayOfClassConstants' => [
'<?php
/**
* @param array<class-string> $arr
*/
function takesClassConstants(array $arr) : void {}
class A {}
class B {}
takesClassConstants([A::class, B::class]);',
],
'arrayOfStringClasses' => [
'<?php
/**
* @param array<class-string> $arr
*/
function takesClassConstants(array $arr) : void {}
class A {}
class B {}
takesClassConstants(["A", "B"]);',
'annotations' => [],
'error_levels' => ['TypeCoercion'],
],
'singleClassConstant' => [
'<?php
/**
* @param class-string $s
*/
function takesClassConstants(string $s) : void {}
class A {}
takesClassConstants(A::class);',
],
'singleClassConstantWithString' => [
'<?php
/**
* @param class-string $s
*/
function takesClassConstants(string $s) : void {}
class A {}
takesClassConstants("A");',
'annotations' => [],
'error_levels' => ['TypeCoercion'],
],
'returnClassConstant' => [
'<?php
class A {}
/**
* @return class-string
*/
function takesClassConstants() : string {
return A::class;
}',
],
'returnClassConstantAllowCoercion' => [
'<?php
class A {}
/**
* @return class-string
*/
function takesClassConstants() : string {
return "A";
}',
'annotations' => [],
'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'],
],
'returnClassConstantArray' => [
'<?php
class A {}
class B {}
/**
* @return array<class-string>
*/
function takesClassConstants() : array {
return [A::class, B::class];
}',
],
'returnClassConstantArrayAllowCoercion' => [
'<?php
class A {}
class B {}
/**
* @return array<class-string>
*/
function takesClassConstants() : array {
return ["A", "B"];
}',
'annotations' => [],
'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'],
],
'allowOptionalParamsToBeEmptyArray' => [
'<?php
/** @param array{b?: int, c?: string} $a */
function foo(array $a = []) : void {}',
],
'allowEmptyVarAnnotation' => [
'<?php
/**
* @param $x
*/
function example(array $x) : void {}',
],
2018-04-04 18:39:05 +02:00
'allowCapitalisedNamespacedString' => [
'<?php
namespace Foo;
/**
* @param String $x
*/
function example(string $x) : void {}',
],
];
}
/**
* @return array
*/
public function providerFileCheckerInvalidCodeParse()
{
return [
'invalidReturn' => [
'<?php
interface I {
/**
* @return $thus
*/
public static function barBar();
}',
'error_message' => 'MissingDocblockType',
],
'invalidReturnClass' => [
'<?php
interface I {
/**
* @return 1
*/
public static function barBar();
}',
'error_message' => 'InvalidDocblock',
],
'invalidReturnBrackets' => [
'<?php
interface I {
/**
* @return []
*/
public static function barBar();
}',
'error_message' => 'InvalidDocblock',
],
'invalidPropertyClass' => [
'<?php
class A {
/**
* @var 1
*/
public $bar;
}',
'error_message' => 'InvalidDocblock',
],
'invalidPropertyBrackets' => [
'<?php
class A {
/**
* @var []
*/
public $bar;
}',
'error_message' => 'InvalidDocblock',
],
'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',
],
'deprecatedMethodWithCall' => [
'<?php
class Foo {
/**
* @deprecated
*/
2018-01-11 21:50:45 +01:00
public static function barBar(): void {
}
}
Foo::barBar();',
2017-05-27 02:05:57 +02:00
'error_message' => 'DeprecatedMethod',
],
2017-05-25 06:34:39 +02:00
'deprecatedClassWithStaticCall' => [
'<?php
/**
* @deprecated
*/
class Foo {
2018-01-11 21:50:45 +01:00
public static function barBar(): void {
2017-05-25 06:34:39 +02:00
}
}
Foo::barBar();',
2017-05-27 02:05:57 +02:00
'error_message' => 'DeprecatedClass',
2017-05-25 06:34:39 +02:00
],
'deprecatedClassWithNew' => [
'<?php
/**
* @deprecated
*/
class Foo { }
$a = new Foo();',
2017-05-27 02:05:57 +02:00
'error_message' => 'DeprecatedClass',
2017-05-25 06:34:39 +02:00
],
'deprecatedClassWithExtends' => [
'<?php
/**
* @deprecated
*/
class Foo { }
class Bar extends Foo {}',
'error_message' => 'DeprecatedClass',
],
'deprecatedPropertyGet' => [
'<?php
class A{
/**
* @deprecated
* @var ?int
*/
public $foo;
}
echo (new A)->foo;',
2017-05-27 02:05:57 +02:00
'error_message' => 'DeprecatedProperty',
],
'deprecatedPropertySet' => [
'<?php
class A{
/**
* @deprecated
* @var ?int
*/
public $foo;
}
$a = new A;
$a->foo = 5;',
2017-05-27 02:05:57 +02:00
'error_message' => 'DeprecatedProperty',
],
'missingParamType' => [
'<?php
/**
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
* @param string $bar
*/
2018-01-11 21:50:45 +01:00
function fooBar(): void {
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
}
fooBar("hello");',
'error_message' => 'TooManyArguments - src/somefile.php:8 - Too many arguments for method fooBar '
. '- expecting 0 but saw 1',
],
'missingParamVar' => [
'<?php
/**
* @param string
*/
2018-01-11 21:50:45 +01:00
function fooBar(): void {
}',
2017-07-27 03:30:01 +02:00
'error_message' => 'InvalidDocblock - src/somefile.php:5 - Badly-formatted @param',
],
'invalidDocblockReturn' => [
'<?php
/**
* @return string
*/
2018-01-11 21:50:45 +01:00
function fooFoo(): int {
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
return 5;
}',
'error_message' => 'MismatchingDocblockReturnType',
],
'propertyDocblockInvalidAssignment' => [
'<?php
/**
* @property string $foo
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
$a->foo = 5;',
'error_message' => 'InvalidPropertyAssignmentValue',
],
2018-02-04 05:10:22 +01:00
'propertyInvalidClassAssignment' => [
'<?php
namespace Bar;
class PropertyType {}
class SomeOtherPropertyType {}
/**
* @property PropertyType $foo
*/
class A {
/** @param string $name */
public function __get($name): ?string {
if ($name === "foo") {
return "hello";
}
}
/**
* @param string $name
* @param mixed $value
*/
public function __set($name, $value): void {
}
}
$a = new A();
$a->foo = new SomeOtherPropertyType();',
'error_message' => 'InvalidPropertyAssignmentValue - src/somefile.php:27 - $a->foo with declared type'
. ' \'Bar\PropertyType\' cannot',
],
'propertyWriteDocblockInvalidAssignment' => [
'<?php
/**
* @property-write string $foo
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
$a->foo = 5;',
'error_message' => 'InvalidPropertyAssignmentValue',
],
'propertySealedDocblockUndefinedPropertyAssignment' => [
'<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
$a->bar = 5;',
'error_message' => 'UndefinedPropertyAssignment',
],
'propertySealedDocblockDefinedPropertyAssignment' => [
'<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
$a->foo = 5;',
'error_message' => 'InvalidPropertyAssignmentValue',
],
'propertyReadInvalidFetch' => [
'<?php
/**
* @property-read string $foo
*/
class A {
/** @return mixed */
public function __get(string $name) {
if ($name === "foo") {
return "hello";
}
}
}
$a = new A();
echo count($a->foo);',
'error_message' => 'InvalidArgument',
],
'propertySealedDocblockUndefinedPropertyFetch' => [
'<?php
/**
* @property string $foo
* @psalm-seal-properties
*/
class A {
2018-01-11 21:50:45 +01:00
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
2018-01-11 21:50:45 +01:00
public function __set(string $name, $value): void {
}
}
$a = new A();
echo $a->bar;',
'error_message' => 'UndefinedPropertyFetch',
],
'noStringParamType' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
echo substr($a, 4, 2);
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
. ' should be string',
'error_levels' => ['MixedArgument'],
],
'noParamTypeButConcat' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
echo $a . "foo";
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
. ' should be string',
'error_levels' => ['MixedOperand'],
],
2017-09-07 03:44:26 +02:00
'noParamTypeButAddition' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
2017-09-07 03:44:26 +02:00
echo $a + 5;
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
2017-09-07 03:44:26 +02:00
. ' should be int|float',
'error_levels' => ['MixedOperand', 'MixedArgument'],
],
'noParamTypeButDivision' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
2017-09-07 03:44:26 +02:00
echo $a / 5;
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
2017-09-07 03:44:26 +02:00
. ' should be int|float',
'error_levels' => ['MixedOperand', 'MixedArgument'],
],
'noParamTypeButTemplatedString' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
echo "$a";
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
. ' should be string',
'error_levels' => ['MixedOperand'],
],
'noStringIntParamType' => [
'<?php
2018-01-11 21:50:45 +01:00
function fooFoo($a): void {
if (is_string($a)) {
echo substr($a, 4, 2);
} else {
echo substr("hello", $a, 2);
}
}',
'error_message' => 'MissingParamType - src/somefile.php:2 - Parameter $a has no provided type,'
. ' should be int|string',
'error_levels' => ['MixedArgument'],
],
'intParamTypeDefinedInParent' => [
'<?php
class A {
2018-01-11 21:50:45 +01:00
public function foo(int $a): void {}
}
class B extends A {
2018-01-11 21:50:45 +01:00
public function foo($a): void {}
}',
'error_message' => 'MissingParamType',
'error_levels' => ['MethodSignatureMismatch'],
],
'alreadyHasCheck' => [
'<?php
2018-01-11 21:50:45 +01:00
function takesString(string $s): void {}
2018-01-11 21:50:45 +01:00
function shouldTakeString($s): void {
if (is_string($s)) takesString($s);
}',
'error_message' => 'MissingParamType - src/somefile.php:4 - Parameter $s has no provided type,'
. ' could not infer',
'error_levels' => ['MixedArgument'],
],
'isSetBeforeInferrence' => [
'input' => '<?php
2018-01-11 21:50:45 +01:00
function takesString(string $s): void {}
/** @return mixed */
function returnsMixed() {}
2018-01-11 21:50:45 +01:00
function shouldTakeString($s): void {
$s = returnsMixed();
takesString($s);
}',
'error_message' => 'MissingParamType - src/somefile.php:7 - Parameter $s has no provided type,'
. ' could not infer',
'error_levels' => ['MixedArgument', 'InvalidReturnType', 'MixedAssignment'],
],
'psalmInvalidVar' => [
'<?php
class A
{
/** @psalm-var array<int, string> */
public $foo = [];
2018-01-11 21:50:45 +01:00
public function updateFoo(): void {
$this->foo["boof"] = "hello";
}
}',
'error_message' => 'InvalidPropertyAssignmentValue',
],
'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 {
/** @var $myVar MyNS\OtherClass */
$myVar = $x->conn()->method();
$myVar->otherMethod();
}',
'error_message' => 'MissingDocblockType',
],
'dontOverrideSameType' => [
'<?php
class A {
/** @return ?int */
2018-01-11 21:50:45 +01:00
public function foo(): ?int {
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 {
2018-01-10 04:46:55 +01:00
return new A;
}',
'error_message' => 'UndefinedClass',
],
'preventBadBoolean' => [
'<?php
2018-01-11 21:50:45 +01:00
function foo(): boolean {
return true;
}',
'error_message' => 'UndefinedClass',
],
'preventBadObjectLikeFormat' => [
'<?php
/**
* @param array{} $arr
*/
function bar(array $arr): void {}',
'error_message' => 'InvalidDocblock',
2018-01-20 17:48:16 +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',
],
/**
* The property $foo is not defined on the object, but accessed with the magic setter.
* This is an error because `@psalm-seal-properties` is specified on the class block.
*/
'magicSetterUndefinedProperty' => [
'<?php
/**
* @psalm-seal-properties
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function badSet(): void {
$this->__set("foo", "value");
}
}',
'error_message' => 'UndefinedThisPropertyAssignment',
],
/**
* The property $foo is not defined on the object, but accessed with the magic getter.
* This is an error because `@psalm-seal-properties` is specified on the class block.
*/
'magicGetterUndefinedProperty' => [
'<?php
/**
* @psalm-seal-properties
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function badGet(): void {
$this->__get("foo");
}
}',
'error_message' => 'UndefinedThisPropertyFetch',
],
/**
* The property $foo is defined as a string with the `@property` annotation, but
* the magic setter is used to set it to an object.
*/
'magicSetterInvalidAssignmentType' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
public function badSet(): void {
$this->__set("foo", new stdClass());
}
}',
'error_message' => 'InvalidPropertyAssignmentValue',
],
'propertyDocblockAssignmentToMixed' => [
'<?php
/**
* @property string $foo
*/
class A {
public function __get(string $name): ?string {
if ($name === "foo") {
return "hello";
}
}
/** @param mixed $value */
public function __set(string $name, $value): void {
}
}
/** @param mixed $b */
function foo($b) : void {
$a = new A();
$a->__set("foo", $b);
}',
'error_message' => 'MixedTypeCoercion',
'error_levels' => ['MixedAssignment'],
],
'arrayOfStringClasses' => [
'<?php
/**
* @param array<class-string> $arr
*/
function takesClassConstants(array $arr) : void {}
class A {}
class B {}
takesClassConstants(["A", "B"]);',
'error_message' => 'TypeCoercion',
],
'arrayOfNonExistentStringClasses' => [
'<?php
/**
* @param array<class-string> $arr
*/
function takesClassConstants(array $arr) : void {}
takesClassConstants(["A", "B"]);',
'error_message' => 'UndefinedClass',
'error_levels' => ['TypeCoercion'],
],
'singleClassConstantWithInvalidDocblock' => [
'<?php
/**
* @param clas-string $s
*/
function takesClassConstants(string $s) : void {}',
'error_message' => 'InvalidDocblock',
],
'returnClassConstantDisallowCoercion' => [
'<?php
class A {}
/**
* @return class-string
*/
function takesClassConstants() : string {
return "A";
}',
'error_message' => 'LessSpecificReturnStatement',
],
'returnClassConstantArrayDisallowCoercion' => [
'<?php
class A {}
/**
* @return array<class-string>
*/
function takesClassConstants() : array {
return ["A", "B"];
}',
'error_message' => 'LessSpecificReturnStatement',
],
'returnClassConstantArrayAllowCoercionWithUndefinedClass' => [
'<?php
class A {}
/**
* @return array<class-string>
*/
function takesClassConstants() : array {
return ["A", "B"];
}',
'error_message' => 'UndefinedClass',
'error_levels' => ['LessSpecificReturnStatement', 'MoreSpecificReturnType'],
],
'badStaticVar' => [
'<?php
/** @var static */
$a = new stdClass();',
'error_message' => 'InvalidDocblock',
],
2018-03-29 08:20:19 +02:00
'doubleBar' => [
'<?php
/** @param PDO||Closure|numeric $a */
function foo($a) : void {}',
'error_message' => 'InvalidDocblock',
],
];
2016-12-31 06:14:00 +01:00
}
2016-12-12 05:41:11 +01:00
}