mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Basic implementation
This commit is contained in:
parent
9cb963f956
commit
acfdb82856
@ -89,6 +89,7 @@
|
||||
<xs:attribute name="reportInfo" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="restrictReturnTypes" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="limitMethodComplexity" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="triggerErrorExits" type="TriggerErrorExitsType" default="default" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="ProjectFilesType">
|
||||
@ -646,4 +647,12 @@
|
||||
<xs:enumeration value="suppress"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="TriggerErrorExitsType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="default"/>
|
||||
<xs:enumeration value="none"/>
|
||||
<xs:enumeration value="always"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
|
@ -547,6 +547,11 @@ class Config
|
||||
/** @var list<ConfigIssue> */
|
||||
public $config_issues = [];
|
||||
|
||||
/**
|
||||
* @var 'default'|'none'|'always'
|
||||
*/
|
||||
public $trigger_error_exits = 'default';
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
self::$instance = $this;
|
||||
@ -844,6 +849,7 @@ class Config
|
||||
'reportInfo' => 'report_info',
|
||||
'restrictReturnTypes' => 'restrict_return_types',
|
||||
'limitMethodComplexity' => 'limit_method_complexity',
|
||||
'triggerErrorExits' => 'trigger_error_exits',
|
||||
];
|
||||
|
||||
foreach ($booleanAttributes as $xmlName => $internalName) {
|
||||
|
@ -102,19 +102,6 @@ class ScopeAnalyzer
|
||||
}
|
||||
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Expression) {
|
||||
if ($stmt->expr instanceof PhpParser\Node\Expr\FuncCall
|
||||
&& $stmt->expr->name instanceof PhpParser\Node\Name
|
||||
&& $stmt->expr->name->parts === ['trigger_error']
|
||||
&& isset($stmt->expr->args[1])
|
||||
&& $stmt->expr->args[1]->value instanceof PhpParser\Node\Expr\ConstFetch
|
||||
&& in_array(
|
||||
end($stmt->expr->args[1]->value->name->parts),
|
||||
['E_ERROR', 'E_PARSE', 'E_CORE_ERROR', 'E_COMPILE_ERROR', 'E_USER_ERROR']
|
||||
)
|
||||
) {
|
||||
return array_values(array_unique(array_merge($control_actions, [self::ACTION_END])));
|
||||
}
|
||||
|
||||
// This allows calls to functions that always exit to act as exit statements themselves
|
||||
if ($nodes
|
||||
&& ($stmt_expr_type = $nodes->getType($stmt->expr))
|
||||
|
@ -71,6 +71,7 @@ class FunctionReturnTypeProvider
|
||||
$this->registerClass(ReturnTypeProvider\FirstArgStringReturnTypeProvider::class);
|
||||
$this->registerClass(ReturnTypeProvider\HexdecReturnTypeProvider::class);
|
||||
$this->registerClass(ReturnTypeProvider\MinMaxReturnTypeProvider::class);
|
||||
$this->registerClass(ReturnTypeProvider\TriggerErrorReturnTypeProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,64 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Psalm\Internal\Provider\ReturnTypeProvider;
|
||||
|
||||
use Psalm\Plugin\EventHandler\Event\FunctionReturnTypeProviderEvent;
|
||||
use Psalm\Type;
|
||||
|
||||
use function in_array;
|
||||
|
||||
use const E_USER_DEPRECATED;
|
||||
use const E_USER_ERROR;
|
||||
use const E_USER_NOTICE;
|
||||
use const E_USER_WARNING;
|
||||
|
||||
class TriggerErrorReturnTypeProvider implements \Psalm\Plugin\EventHandler\FunctionReturnTypeProviderInterface
|
||||
{
|
||||
/**
|
||||
* @return array<lowercase-string>
|
||||
*/
|
||||
public static function getFunctionIds(): array
|
||||
{
|
||||
return ['trigger_error'];
|
||||
}
|
||||
|
||||
public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $event): ?Type\Union
|
||||
{
|
||||
$config = $event->getStatementsSource()->getCodebase()->config;
|
||||
if ($config->trigger_error_exits === 'always') {
|
||||
return new Type\Union([new Type\Atomic\TNever()]);
|
||||
}
|
||||
|
||||
if ($config->trigger_error_exits === 'none') {
|
||||
return new Type\Union([new Type\Atomic\TTrue()]);
|
||||
}
|
||||
|
||||
//default behaviour
|
||||
$call_args = $event->getCallArgs();
|
||||
$statements_source = $event->getStatementsSource();
|
||||
if (isset($call_args[1])
|
||||
&& ($array_arg_type = $statements_source->getNodeTypeProvider()->getType($call_args[1]->value))
|
||||
) {
|
||||
$return_types = [];
|
||||
foreach ($array_arg_type->getAtomicTypes() as $atomicType) {
|
||||
if ($atomicType instanceof Type\Atomic\TLiteralInt) {
|
||||
if (in_array($atomicType->value, [E_USER_WARNING, E_USER_DEPRECATED, E_USER_NOTICE], true)) {
|
||||
$return_types[] = new Type\Atomic\TTrue();
|
||||
} elseif ($atomicType->value === E_USER_ERROR) {
|
||||
$return_types[] = new Type\Atomic\TNever();
|
||||
} else {
|
||||
// not recognized int literal. return false before PHP8, fatal error since
|
||||
$return_types[] = new Type\Atomic\TFalse();
|
||||
}
|
||||
} else {
|
||||
$return_types[] = new Type\Atomic\TBool();
|
||||
}
|
||||
}
|
||||
|
||||
return new Type\Union($return_types);
|
||||
}
|
||||
|
||||
//default value is E_USER_NOTICE, so return true
|
||||
return new Type\Union([new Type\Atomic\TTrue()]);
|
||||
}
|
||||
}
|
@ -2126,4 +2126,83 @@ class FunctionCallTest extends TestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testTriggerErrorDefault(): void
|
||||
{
|
||||
$config = \Psalm\Config::getInstance();
|
||||
$config->trigger_error_exits = 'default';
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
/** @return true */
|
||||
function returnsTrue(): bool {
|
||||
return trigger_error("", E_USER_NOTICE);
|
||||
}
|
||||
/** @return never */
|
||||
function returnsNever(): void {
|
||||
trigger_error("", E_USER_ERROR);
|
||||
}
|
||||
/**
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
* @return mixed
|
||||
*/
|
||||
function returnsNeverOrBool(int $i) {
|
||||
return trigger_error("", $i);
|
||||
}'
|
||||
);
|
||||
|
||||
//will only pass if no exception is thrown
|
||||
$this->assertTrue(true);
|
||||
|
||||
$this->analyzeFile('somefile.php', new \Psalm\Context());
|
||||
}
|
||||
|
||||
public function testTriggerErrorAlways(): void
|
||||
{
|
||||
$config = \Psalm\Config::getInstance();
|
||||
$config->trigger_error_exits = 'always';
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
/** @return never */
|
||||
function returnsNever1(): void {
|
||||
trigger_error("", E_USER_NOTICE);
|
||||
}
|
||||
/** @return never */
|
||||
function returnsNever2(): void {
|
||||
trigger_error("", E_USER_ERROR);
|
||||
}'
|
||||
);
|
||||
|
||||
//will only pass if no exception is thrown
|
||||
$this->assertTrue(true);
|
||||
|
||||
$this->analyzeFile('somefile.php', new \Psalm\Context());
|
||||
}
|
||||
|
||||
public function testTriggerErrorNone(): void
|
||||
{
|
||||
$config = \Psalm\Config::getInstance();
|
||||
$config->trigger_error_exits = 'none';
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
/** @return true */
|
||||
function returnsTrue1(): bool {
|
||||
return trigger_error("", E_USER_NOTICE);
|
||||
}
|
||||
/** @return true */
|
||||
function returnsTrue2(): bool {
|
||||
return trigger_error("", E_USER_ERROR);
|
||||
}'
|
||||
);
|
||||
|
||||
//will only pass if no exception is thrown
|
||||
$this->assertTrue(true);
|
||||
|
||||
$this->analyzeFile('somefile.php', new \Psalm\Context());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user