mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #479 - allow PhpStorm generic syntax behind a config flag
This commit is contained in:
parent
fb300baa6d
commit
8d2baf584e
@ -28,6 +28,7 @@
|
|||||||
<xs:attribute name="cacheFileContentHashes" type="xs:string" />
|
<xs:attribute name="cacheFileContentHashes" type="xs:string" />
|
||||||
<xs:attribute name="rememberPropertyAssignmentsAfterCall" type="xs:string" />
|
<xs:attribute name="rememberPropertyAssignmentsAfterCall" type="xs:string" />
|
||||||
<xs:attribute name="serializer" type="xs:string" />
|
<xs:attribute name="serializer" type="xs:string" />
|
||||||
|
<xs:attribute name="allowPhpStormGenerics" type="xs:string" />
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="ProjectFilesType">
|
<xs:complexType name="ProjectFilesType">
|
||||||
|
@ -383,6 +383,42 @@ class Codebase
|
|||||||
$this->populateFileStorage($file_storage);
|
$this->populateFileStorage($file_storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->config->allow_phpstorm_generics) {
|
||||||
|
foreach ($this->classlike_storage_provider->getAll() as $class_storage) {
|
||||||
|
foreach ($class_storage->properties as $property_storage) {
|
||||||
|
if ($property_storage->type) {
|
||||||
|
$this->convertPhpStormGenericToPsalmGeneric($property_storage->type, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($class_storage->methods as $method_storage) {
|
||||||
|
if ($method_storage->return_type) {
|
||||||
|
$this->convertPhpStormGenericToPsalmGeneric($method_storage->return_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($method_storage->params as $param_storage) {
|
||||||
|
if ($param_storage->type) {
|
||||||
|
$this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($all_file_storage as $file_storage) {
|
||||||
|
foreach ($file_storage->functions as $function_storage) {
|
||||||
|
if ($function_storage->return_type) {
|
||||||
|
$this->convertPhpStormGenericToPsalmGeneric($function_storage->return_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($function_storage->params as $param_storage) {
|
||||||
|
if ($param_storage->type) {
|
||||||
|
$this->convertPhpStormGenericToPsalmGeneric($param_storage->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->debug_output) {
|
if ($this->debug_output) {
|
||||||
echo 'FileStorage is populated' . PHP_EOL;
|
echo 'FileStorage is populated' . PHP_EOL;
|
||||||
}
|
}
|
||||||
@ -629,6 +665,49 @@ class Codebase
|
|||||||
$storage->populated = true;
|
$storage->populated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Type\Union $candidate
|
||||||
|
* @param bool $is_property
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function convertPhpStormGenericToPsalmGeneric(Type\Union $candidate, $is_property = false)
|
||||||
|
{
|
||||||
|
$atomic_types = $candidate->getTypes();
|
||||||
|
|
||||||
|
if (isset($atomic_types['array']) && count($atomic_types) > 1) {
|
||||||
|
$iterator_name = null;
|
||||||
|
$generic_params = null;
|
||||||
|
|
||||||
|
foreach ($atomic_types as $type) {
|
||||||
|
if ($type instanceof Type\Atomic\TNamedObject
|
||||||
|
&& (!$type->from_docblock || $is_property)
|
||||||
|
&& (
|
||||||
|
strtolower($type->value) === 'traversable'
|
||||||
|
|| $this->interfaceExtends(
|
||||||
|
$type->value,
|
||||||
|
'Traversable'
|
||||||
|
)
|
||||||
|
|| $this->classImplements(
|
||||||
|
$type->value,
|
||||||
|
'Traversable'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$iterator_name = $type->value;
|
||||||
|
} elseif ($type instanceof Type\Atomic\TArray) {
|
||||||
|
$generic_params = $type->type_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iterator_name && $generic_params) {
|
||||||
|
$generic_iterator = new Type\Atomic\TGenericObject($iterator_name, $generic_params);
|
||||||
|
$candidate->removeType('array');
|
||||||
|
$candidate->addType($generic_iterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ClassLikeStorage $storage
|
* @param ClassLikeStorage $storage
|
||||||
* @param ClassLikeStorage $parent_storage
|
* @param ClassLikeStorage $parent_storage
|
||||||
|
@ -172,6 +172,11 @@ class Config
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
public $use_igbinary = false;
|
public $use_igbinary = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $allow_phpstorm_generics = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Psalm plugins
|
* Psalm plugins
|
||||||
*
|
*
|
||||||
@ -382,6 +387,11 @@ class Config
|
|||||||
$config->use_igbinary = $attribute_text === 'igbinary';
|
$config->use_igbinary = $attribute_text === 'igbinary';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($config_xml['allowPhpStormGenerics'])) {
|
||||||
|
$attribute_text = (string) $config_xml['allowPhpStormGenerics'];
|
||||||
|
$config->allow_phpstorm_generics = $attribute_text === 'true' || $attribute_text === '1';
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($config_xml->projectFiles)) {
|
if (isset($config_xml->projectFiles)) {
|
||||||
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true);
|
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,87 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace Psalm\Tests;
|
namespace Psalm\Tests;
|
||||||
|
|
||||||
|
use Psalm\Config;
|
||||||
|
use Psalm\Context;
|
||||||
|
|
||||||
class AnnotationTest extends TestCase
|
class AnnotationTest extends TestCase
|
||||||
{
|
{
|
||||||
use Traits\FileCheckerInvalidCodeParseTestTrait;
|
use Traits\FileCheckerInvalidCodeParseTestTrait;
|
||||||
use Traits\FileCheckerValidCodeParseTestTrait;
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -733,6 +809,20 @@ class AnnotationTest extends TestCase
|
|||||||
function bar(array $arr): void {}',
|
function bar(array $arr): void {}',
|
||||||
'error_message' => 'InvalidDocblock',
|
'error_message' => 'InvalidDocblock',
|
||||||
],
|
],
|
||||||
|
'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',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user