mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
feature: universal object crates (#3948)
* feature: universal object crates * docs: document universal object crate config option Co-authored-by: Matthew Brown <github@muglug.com>
This commit is contained in:
parent
c4cbe8dfdc
commit
a19f738967
@ -21,6 +21,7 @@
|
||||
<xs:element name="issueHandlers" type="IssueHandlersType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="ignoreExceptions" type="ExceptionsType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="globals" type="GlobalsType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="universalObjectCrates" type="UniversalObjectCratesType" minOccurs="0" maxOccurs="1" />
|
||||
</xs:choice>
|
||||
|
||||
<xs:attribute name="autoloader" type="xs:string" />
|
||||
@ -128,6 +129,12 @@
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="UniversalObjectCratesType">
|
||||
<xs:sequence>
|
||||
<xs:element name="class" maxOccurs="unbounded" type="NameAttributeType" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ExceptionsType">
|
||||
<xs:sequence>
|
||||
<xs:element name="class" minOccurs="0" maxOccurs="unbounded" type="ExceptionType" />
|
||||
|
@ -387,6 +387,9 @@ Optional. If you don't want Psalm to complain about every single issue it finds
|
||||
#### <mockClasses>
|
||||
Optional. Do you use mock classes in your tests? If you want Psalm to ignore them when checking files, include a fully-qualified path to the class with `<class name="Your\Namespace\ClassName" />`
|
||||
|
||||
#### <universalObjectCrates>
|
||||
Optional. Do you have objects with properties that cannot be determined statically? If you want Psalm to treat all properties on a given classlike as mixed, include a fully-qualified path to the class with `<class name="Your\Namespace\ClassName" />`. By default, `stdClass` and `SimpleXMLElement` are configured to be universal object crates.
|
||||
|
||||
#### <stubs>
|
||||
Optional. If your codebase uses classes and functions that are not visible to Psalm via reflection (e.g. if there are internal packages that your codebase relies on that are not available on the machine running Psalm), you can use stub files. Used by PhpStorm (a popular IDE) and others, stubs provide a description of classes and functions without the implementations. You can find a list of stubs for common classes [here](https://github.com/JetBrains/phpstorm-stubs). List out each file with `<file name="path/to/file.php" />`.
|
||||
|
||||
|
@ -84,6 +84,7 @@ use const LIBXML_ERR_FATAL;
|
||||
use const LIBXML_NONET;
|
||||
use const PHP_EOL;
|
||||
use const SCANDIR_SORT_NONE;
|
||||
use function array_map;
|
||||
|
||||
/**
|
||||
* @psalm-suppress PropertyNotSetInConstructor
|
||||
@ -128,6 +129,15 @@ class Config
|
||||
'MixedReturnTypeCoercion',
|
||||
];
|
||||
|
||||
/**
|
||||
* These are special object classes that allow any and all properties to be get/set on them
|
||||
* @var array<int, class-string>
|
||||
*/
|
||||
protected $universal_object_crates = [
|
||||
\stdClass::class,
|
||||
SimpleXMLElement::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var static|null
|
||||
*/
|
||||
@ -953,6 +963,15 @@ class Config
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config_xml->universalObjectCrates) && isset($config_xml->universalObjectCrates->class)) {
|
||||
/** @var \SimpleXMLElement $universal_object_crate */
|
||||
foreach ($config_xml->universalObjectCrates->class as $universal_object_crate) {
|
||||
/** @var class-string $classString */
|
||||
$classString = $universal_object_crate['name'];
|
||||
$config->addUniversalObjectCrate($classString);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config_xml->ignoreExceptions)) {
|
||||
if (isset($config_xml->ignoreExceptions->class)) {
|
||||
/** @var \SimpleXMLElement $exception_class */
|
||||
@ -1987,4 +2006,20 @@ class Config
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
*/
|
||||
public function addUniversalObjectCrate(string $class): void
|
||||
{
|
||||
$this->universal_object_crates[] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, lowercase-string>
|
||||
*/
|
||||
public function getUniversalObjectCrates(): array
|
||||
{
|
||||
return array_map('strtolower', $this->universal_object_crates);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression\Assignment;
|
||||
use PhpParser;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\PropertyProperty;
|
||||
use Psalm\Config;
|
||||
use Psalm\Internal\Analyzer\ClassAnalyzer;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
@ -269,7 +270,11 @@ class InstancePropertyAssignmentAnalyzer
|
||||
(
|
||||
in_array(
|
||||
strtolower($lhs_type_part->value),
|
||||
['stdclass', 'simplexmlelement', 'dateinterval', 'domdocument', 'domnode'],
|
||||
Config::getInstance()->getUniversalObjectCrates() + [
|
||||
'dateinterval',
|
||||
'domdocument',
|
||||
'domnode'
|
||||
],
|
||||
true
|
||||
)
|
||||
)
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression\Fetch;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Config;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
@ -427,7 +428,7 @@ class InstancePropertyFetchAnalyzer
|
||||
// but we don't want to throw an error
|
||||
// Hack has a similar issue: https://github.com/facebook/hhvm/issues/5164
|
||||
if ($lhs_type_part instanceof TObject
|
||||
|| in_array(strtolower($lhs_type_part->value), ['stdclass', 'simplexmlelement'], true)
|
||||
|| in_array(strtolower($lhs_type_part->value), Config::getInstance()->getUniversalObjectCrates(), true)
|
||||
) {
|
||||
$statements_analyzer->node_data->setType($stmt, Type::getMixed());
|
||||
|
||||
|
@ -1290,4 +1290,22 @@ class ConfigTest extends \Psalm\Tests\TestCase
|
||||
|
||||
$this->assertFalse($this->project_analyzer->getConfig()->use_phpstorm_meta_path);
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
public function testSetsUniversalObjectCrates()
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__, 2),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<universalObjectCrates>
|
||||
<class name="Foo" />
|
||||
</universalObjectCrates>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertContains('foo', $this->project_analyzer->getConfig()->getUniversalObjectCrates());
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +118,35 @@ class PropertyTypeTest extends TestCase
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testUniversalObjectCrates(): void
|
||||
{
|
||||
/** @var class-string $classString */
|
||||
$classString = 'Foo';
|
||||
Config::getInstance()->addUniversalObjectCrate($classString);
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class Foo { }
|
||||
|
||||
$f = new Foo();
|
||||
// reads are fine
|
||||
$f->bar;
|
||||
|
||||
// sets are fine
|
||||
$f->buzz = false;
|
||||
'
|
||||
);
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testForgetPropertyAssignmentsInBranchWithThrowNormally(): void
|
||||
{
|
||||
$this->addFile(
|
||||
|
Loading…
Reference in New Issue
Block a user