mirror of
https://github.com/danog/psalm.git
synced 2024-12-11 16:59:45 +01:00
Merge branch '4.x' into feature/upgrade-lsp
This commit is contained in:
commit
c679692e06
@ -2,7 +2,7 @@
|
||||
<psalm
|
||||
name="Psalm for Psalm"
|
||||
useDocblockTypes="true"
|
||||
totallyTyped="true"
|
||||
errorLevel=1
|
||||
strictBinaryOperands="false"
|
||||
rememberPropertyAssignmentsAfterCall="true"
|
||||
throwExceptionOnError="0"
|
||||
|
13
config.xsd
13
config.xsd
@ -97,7 +97,18 @@
|
||||
<xs:attribute name="resolveFromConfigFile" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="strictBinaryOperands" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="throwExceptionOnError" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="totallyTyped" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="totallyTyped" type="xs:boolean" default="false">
|
||||
<xs:annotation>
|
||||
<xs:documentation xml:lang="en">
|
||||
Setting `totallyTyped` to `"true"` is equivalent to setting `errorLevel` to `"1"`. Setting `totallyTyped` to `"false"` is equivalent to setting `errorLevel` to `"2"` and `reportMixedIssues` to `"false"`
|
||||
</xs:documentation>
|
||||
|
||||
<!-- note: for PHPStorm to mark the attribute as deprecated the doc entry has to be *single line* and start with the word `deprecated` -->
|
||||
<xs:documentation xml:lang="en">
|
||||
Deprecated. Replaced by `errorLevel` and `reportMixedIssues`.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="errorLevel" type="xs:integer" default="2" />
|
||||
<xs:attribute name="reportMixedIssues" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="useDocblockTypes" type="xs:boolean" default="true" />
|
||||
|
@ -2535,7 +2535,7 @@ return [
|
||||
'error_log' => ['bool', 'message'=>'string', 'message_type='=>'int', 'destination='=>'string', 'additional_headers='=>'string'],
|
||||
'error_reporting' => ['int', 'error_level='=>'int'],
|
||||
'ErrorException::__clone' => ['void'],
|
||||
'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'lineno='=>'int', 'previous='=>'?Throwable|?ErrorException'],
|
||||
'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'line='=>'int', 'previous='=>'?Throwable|?ErrorException'],
|
||||
'ErrorException::__toString' => ['string'],
|
||||
'ErrorException::getCode' => ['int'],
|
||||
'ErrorException::getFile' => ['string'],
|
||||
|
@ -1382,7 +1382,7 @@ return [
|
||||
'Error::getTrace' => ['list<array<string,mixed>>'],
|
||||
'Error::getTraceAsString' => ['string'],
|
||||
'ErrorException::__clone' => ['void'],
|
||||
'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'lineno='=>'int', 'previous='=>'?Throwable|?ErrorException'],
|
||||
'ErrorException::__construct' => ['void', 'message='=>'string', 'code='=>'int', 'severity='=>'int', 'filename='=>'string', 'line='=>'int', 'previous='=>'?Throwable|?ErrorException'],
|
||||
'ErrorException::__toString' => ['string'],
|
||||
'ErrorException::getCode' => ['int'],
|
||||
'ErrorException::getFile' => ['string'],
|
||||
|
@ -256,7 +256,7 @@ return [
|
||||
'client_info' => 'string',
|
||||
'client_version' => 'int',
|
||||
'connect_errno' => 'int',
|
||||
'connect_error' => 'string',
|
||||
'connect_error' => '?string',
|
||||
'errno' => 'int',
|
||||
'error' => 'string',
|
||||
'error_list' => 'array',
|
||||
|
@ -416,6 +416,13 @@ Whether or not to allow `require`/`include` calls in your PHP. Defaults to `true
|
||||
```
|
||||
Allows you to hard-code a serializer for Psalm to use when caching data. By default, Psalm uses `ext-igbinary` *if* the version is greater than or equal to 2.0.5, otherwise it defaults to PHP's built-in serializer.
|
||||
|
||||
#### threads
|
||||
```xml
|
||||
<psalm
|
||||
threads="[int]"
|
||||
>
|
||||
```
|
||||
Allows you to hard-code the number of threads Psalm will use (similar to `--threads` on the command line). This value will be used in place of detecting threads from the host machine, but will be overridden by using `--threads` or `--debug` (which sets threads to 1) on the command line
|
||||
|
||||
## Project settings
|
||||
|
||||
|
@ -584,6 +584,9 @@ class Config
|
||||
*/
|
||||
public $internal_stubs = [];
|
||||
|
||||
/** @var ?int */
|
||||
public $threads;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
self::$instance = $this;
|
||||
@ -811,7 +814,9 @@ class Config
|
||||
$deprecated_attributes = [
|
||||
'allowCoercionFromStringToClassConst',
|
||||
'allowPhpStormGenerics',
|
||||
'forbidEcho'
|
||||
'forbidEcho',
|
||||
'loadXdebugStub',
|
||||
'totallyTyped'
|
||||
];
|
||||
|
||||
$deprecated_elements = [
|
||||
@ -1241,6 +1246,10 @@ class Config
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config_xml->threads)) {
|
||||
$config->threads = (int)$config_xml->threads;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
@ -1781,11 +1790,22 @@ class Config
|
||||
|
||||
public function getReportingLevelForFunction(string $issue_type, string $function_id): ?string
|
||||
{
|
||||
$level = null;
|
||||
if (isset($this->issue_handlers[$issue_type])) {
|
||||
return $this->issue_handlers[$issue_type]->getReportingLevelForFunction($function_id);
|
||||
$level = $this->issue_handlers[$issue_type]->getReportingLevelForFunction($function_id);
|
||||
|
||||
if ($level === null && $issue_type === 'UndefinedFunction') {
|
||||
// undefined functions trigger global namespace fallback
|
||||
// so we should also check reporting levels for the symbol in global scope
|
||||
$root_function_id = preg_replace('/.*\\\/', '', $function_id);
|
||||
if ($root_function_id !== $function_id) {
|
||||
/** @psalm-suppress PossiblyUndefinedStringArrayOffset https://github.com/vimeo/psalm/issues/7656 */
|
||||
$level = $this->issue_handlers[$issue_type]->getReportingLevelForFunction($root_function_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return $level;
|
||||
}
|
||||
|
||||
public function getReportingLevelForArgument(string $issue_type, string $function_id): ?string
|
||||
@ -2009,6 +2029,12 @@ class Config
|
||||
$this->internal_stubs[] = $ext_decimal_path;
|
||||
}
|
||||
|
||||
// phpredis
|
||||
if (extension_loaded('redis')) {
|
||||
$ext_phpredis_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'phpredis.phpstub';
|
||||
$this->internal_stubs[] = $ext_phpredis_path;
|
||||
}
|
||||
|
||||
foreach ($this->internal_stubs as $stub_path) {
|
||||
if (!file_exists($stub_path)) {
|
||||
throw new UnexpectedValueException('Cannot locate ' . $stub_path);
|
||||
|
@ -108,6 +108,13 @@ class Context
|
||||
*/
|
||||
public $inside_assignment = false;
|
||||
|
||||
/**
|
||||
* Whether or not we're inside a try block.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $inside_try = false;
|
||||
|
||||
/**
|
||||
* @var null|CodeLocation
|
||||
*/
|
||||
|
@ -86,9 +86,12 @@ class TryAnalyzer
|
||||
|
||||
$old_referenced_var_ids = $try_context->referenced_var_ids;
|
||||
|
||||
$was_inside_try = $context->inside_try;
|
||||
$context->inside_try = true;
|
||||
if ($statements_analyzer->analyze($stmt->stmts, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
$context->inside_try = $was_inside_try;
|
||||
|
||||
if ($try_context->finally_scope) {
|
||||
foreach ($context->vars_in_scope as $var_id => $type) {
|
||||
|
@ -315,6 +315,14 @@ class AssignmentAnalyzer
|
||||
$assign_value_type->parent_nodes = [
|
||||
$assignment_node->id => $assignment_node
|
||||
];
|
||||
|
||||
if ($context->inside_try) {
|
||||
// Copy previous assignment's parent nodes inside a try. Since an exception could be thrown at any
|
||||
// point this is a workaround to ensure that use of a variable also uses all previous assignments.
|
||||
if (isset($context->vars_in_scope[$array_var_id])) {
|
||||
$assign_value_type->parent_nodes += $context->vars_in_scope[$array_var_id]->parent_nodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($array_var_id && isset($context->vars_in_scope[$array_var_id])) {
|
||||
|
@ -16,6 +16,8 @@ use Psalm\Type\Atomic\TCallableString;
|
||||
use Psalm\Type\Atomic\TNonEmptyString;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function dirname;
|
||||
|
||||
class MagicConstAnalyzer
|
||||
{
|
||||
public static function analyze(
|
||||
@ -84,10 +86,16 @@ class MagicConstAnalyzer
|
||||
} else {
|
||||
$statements_analyzer->node_data->setType($stmt, new Union([new TCallableString]));
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File
|
||||
|| $stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir
|
||||
) {
|
||||
$statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()]));
|
||||
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Dir) {
|
||||
$statements_analyzer->node_data->setType(
|
||||
$stmt,
|
||||
Type::getString(dirname($statements_analyzer->getSource()->getFilePath()))
|
||||
);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\File) {
|
||||
$statements_analyzer->node_data->setType(
|
||||
$stmt,
|
||||
Type::getString($statements_analyzer->getSource()->getFilePath())
|
||||
);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Scalar\MagicConst\Trait_) {
|
||||
if ($statements_analyzer->getSource() instanceof TraitAnalyzer) {
|
||||
$statements_analyzer->node_data->setType($stmt, new Union([new TNonEmptyString()]));
|
||||
|
@ -246,7 +246,7 @@ final class Psalm
|
||||
$options['long-progress'] = true;
|
||||
}
|
||||
|
||||
$threads = self::detectThreads($options, $in_ci);
|
||||
$threads = self::detectThreads($options, $config, $in_ci);
|
||||
|
||||
self::emitMacPcreWarning($options, $threads);
|
||||
|
||||
@ -909,12 +909,14 @@ final class Psalm
|
||||
}
|
||||
}
|
||||
|
||||
private static function detectThreads(array $options, bool $in_ci): int
|
||||
private static function detectThreads(array $options, Config $config, bool $in_ci): int
|
||||
{
|
||||
if (isset($options['threads'])) {
|
||||
$threads = (int)$options['threads'];
|
||||
} elseif (isset($options['debug']) || $in_ci) {
|
||||
$threads = 1;
|
||||
} elseif ($config->threads) {
|
||||
$threads = $config->threads;
|
||||
} else {
|
||||
$threads = max(1, ProjectAnalyzer::getCpuCount() - 1);
|
||||
}
|
||||
|
@ -691,7 +691,7 @@ class IssueBuffer
|
||||
: $error_count . ' errors'
|
||||
) . ' found' . "\n";
|
||||
} else {
|
||||
self::printSuccessMessage();
|
||||
self::printSuccessMessage($project_analyzer);
|
||||
}
|
||||
|
||||
$show_info = $project_analyzer->stdout_report_options->show_info;
|
||||
@ -782,8 +782,12 @@ class IssueBuffer
|
||||
}
|
||||
}
|
||||
|
||||
public static function printSuccessMessage(): void
|
||||
public static function printSuccessMessage(ProjectAnalyzer $project_analyzer): void
|
||||
{
|
||||
if (!$project_analyzer->stdout_report_options) {
|
||||
throw new UnexpectedValueException('Cannot print success message without stdout report options');
|
||||
}
|
||||
|
||||
// this message will be printed
|
||||
$message = "No errors found!";
|
||||
|
||||
@ -808,9 +812,15 @@ class IssueBuffer
|
||||
// text style, 1 = bold
|
||||
$style = "1";
|
||||
|
||||
echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n";
|
||||
echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n";
|
||||
echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n";
|
||||
if ($project_analyzer->stdout_report_options->use_color) {
|
||||
echo "\e[{$background};{$style}m{$paddingTop}\e[0m" . "\n";
|
||||
echo "\e[{$background};{$foreground};{$style}m{$messageWithPadding}\e[0m" . "\n";
|
||||
echo "\e[{$background};{$style}m{$paddingBottom}\e[0m" . "\n";
|
||||
} else {
|
||||
echo "\n";
|
||||
echo "$messageWithPadding\n";
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,6 +56,13 @@ namespace {
|
||||
*/
|
||||
public function getBackingValue(): int|string;
|
||||
}
|
||||
|
||||
class ReflectionIntersectionType extends ReflectionType {
|
||||
/**
|
||||
* @return non-empty-list<ReflectionType>
|
||||
*/
|
||||
public function getTypes() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FTP {
|
||||
|
542
stubs/phpredis.phpstub
Normal file
542
stubs/phpredis.phpstub
Normal file
@ -0,0 +1,542 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @generate-function-entries
|
||||
* @generate-legacy-arginfo
|
||||
*/
|
||||
|
||||
class Redis {
|
||||
|
||||
public function __construct(array $options = null);
|
||||
|
||||
public function _compress(string $value): string;
|
||||
|
||||
public function __destruct();
|
||||
|
||||
public function _pack(mixed $value): string;
|
||||
|
||||
public function _prefix(string $key): string;
|
||||
|
||||
public function _serialize(mixed $value): string;
|
||||
|
||||
public function _uncompress(string $value): string;
|
||||
|
||||
public function _unpack(string $value): mixed;
|
||||
|
||||
public function _unserialize(string $value): mixed;
|
||||
|
||||
/**
|
||||
* @param string $args
|
||||
* @return mixed|Redis
|
||||
*/
|
||||
public function acl(string $subcmd, ...$args);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function append(string $key, mixed $value);
|
||||
|
||||
public function auth(mixed $credentials): bool;
|
||||
|
||||
public function bgSave(): bool;
|
||||
|
||||
public function bgrewriteaof(): bool;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function bitcount(string $key, int $start = 0, int $end = -1);
|
||||
|
||||
/**
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function bitop(string $operation, string $deskey, string $srckey, string ...$other_keys): int;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function bitpos(string $key, int $bit, int $start = 0, int $end = -1);
|
||||
|
||||
public function blPop(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array|null|false;
|
||||
|
||||
public function brPop(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array|null|false;
|
||||
|
||||
public function brpoplpush(string $src, string $dst, int $timeout): Redis|string|false;
|
||||
|
||||
public function bzPopMax(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array;
|
||||
|
||||
public function bzPopMin(string|array $key, string|int $timeout_or_key, mixed ...$extra_args): array;
|
||||
|
||||
public function clearLastError(): bool;
|
||||
|
||||
public function client(string $opt, string $arg = null): mixed;
|
||||
|
||||
public function close(): bool;
|
||||
|
||||
public function command(string $opt = null, string|array $arg): mixed;
|
||||
|
||||
public function config(string $operation, string $key, mixed $value = null): mixed;
|
||||
|
||||
public function connect(string $host, int $port = 26379, float $timeout = 0, string $persistent_id = null, int $retry_interval = 0, float $read_timeout = 0, array $context = null): bool;
|
||||
|
||||
public function copy(string $src, string $dst, array $options = null): bool;
|
||||
|
||||
public function dbSize(): int;
|
||||
|
||||
public function debug(string $key): string;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function decr(string $key, int $by = 1);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function decrBy(string $key, int $value);
|
||||
|
||||
/**
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function del(array|string $key, string ...$other_keys);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @alias Redis::del
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function delete(array|string $key, string ...$other_keys);
|
||||
|
||||
public function discard(): bool;
|
||||
|
||||
public function dump(string $key): string;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function echo(string $str);
|
||||
|
||||
public function eval(string $script, array $keys = null, int $num_keys = 0): mixed;
|
||||
|
||||
public function evalsha(string $sha1, array $keys = null, int $num_keys = 0): mixed;
|
||||
|
||||
public function exec(): Redis|array|false;
|
||||
|
||||
/** @return int|Redis|bool */
|
||||
public function exists(mixed $key, mixed ...$other_keys);
|
||||
|
||||
public function expire(string $key, int $timeout): Redis|bool;
|
||||
|
||||
public function expireAt(string $key, int $timestamp): Redis|bool;
|
||||
|
||||
public function flushAll(bool $async = false): bool;
|
||||
|
||||
public function flushDB(bool $async = false): bool;
|
||||
|
||||
public function geoadd(string $key, float $lng, float $lat, string $member, mixed ...$other_triples): int;
|
||||
|
||||
public function geodist(string $key, string $src, string $dst, ?string $unit = null): Redis|float|false;
|
||||
|
||||
public function geohash(string $key, string $member, string ...$other_members): array;
|
||||
|
||||
public function geopos(string $key, string $member, string ...$other_members): Redis|array|false;
|
||||
|
||||
public function georadius(string $key, float $lng, float $lat, float $radius, string $unit, array $options = []): Redis|mixed|false;
|
||||
|
||||
public function georadius_ro(string $key, float $lng, float $lat, float $radius, string $unit, array $options = []): Redis|mixed|false;
|
||||
|
||||
public function georadiusbymember(string $key, string $member, float $radius, string $unit, array $options = []): Redis|mixed|false;
|
||||
|
||||
public function georadiusbymember_ro(string $key, string $member, float $radius, string $unit, array $options = []): Redis|mixed|false;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function get(string $key);
|
||||
|
||||
public function getAuth(): mixed;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function getBit(string $key, int $idx);
|
||||
|
||||
public function getDBNum(): int;
|
||||
|
||||
public function getHost(): string;
|
||||
|
||||
public function getLastError(): ?string;
|
||||
|
||||
public function getMode(): int;
|
||||
|
||||
public function getOption(int $option): mixed;
|
||||
|
||||
public function getPersistentID(): ?string;
|
||||
|
||||
public function getPort(): int;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function getRange(string $key, int $start, int $end);
|
||||
|
||||
public function getReadTimeout(): int;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function getset(string $key, mixed $value);
|
||||
|
||||
public function getTimeout(): int;
|
||||
|
||||
public function hDel(string $key, string $member, string ...$other_members): Redis|int|false;
|
||||
|
||||
public function hExists(string $key, string $member): Redis|bool;
|
||||
|
||||
public function hGet(string $key, string $member): Redis|mixed|false;
|
||||
|
||||
public function hGetAll(string $key): Redis|array|false;
|
||||
|
||||
public function hIncrBy(string $key, string $member, int $value): Redis|int|false;
|
||||
|
||||
public function hIncrByFloat(string $key, string $member, float $value): Redis|float|false;
|
||||
|
||||
public function hKeys(string $key): Redis|array|false;
|
||||
|
||||
public function hLen(string $key): Redis|int|false;
|
||||
|
||||
public function hMget(string $key, array $keys): Redis|array|false;
|
||||
|
||||
public function hMset(string $key, array $keyvals): Redis|bool|false;
|
||||
|
||||
public function hSet(string $key, string $member, mixed $value): Redis|int|false;
|
||||
|
||||
public function hSetNx(string $key, string $member, string $value): Redis|bool;
|
||||
|
||||
public function hStrLen(string $key, string $member): int;
|
||||
|
||||
public function hVals(string $key): Redis|array|false;
|
||||
|
||||
public function hscan(string $key, int &$iterator, ?string $pattern = null, int $count = 0): bool|array;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function incr(string $key, int $by = 1);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function incrBy(string $key, int $value);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function incrByFloat(string $key, float $value);
|
||||
|
||||
public function info(string $opt = null): Redis|array|false;
|
||||
|
||||
public function isConnected(): bool;
|
||||
|
||||
/** @return array|Redis */
|
||||
public function keys(string $pattern);
|
||||
|
||||
/**
|
||||
* @param mixed $elements
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function lInsert(string $key, string $pos, mixed $pivot, mixed $value);
|
||||
|
||||
|
||||
public function lLen(string $key): Redis|int|false;
|
||||
|
||||
public function lMove(string $src, string $dst, string $wherefrom, string $whereto): string;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function lPop(string $key);
|
||||
|
||||
/**
|
||||
* @param mixed $elements
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function lPush(string $key, ...$elements);
|
||||
|
||||
/**
|
||||
* @param mixed $elements
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function rPush(string $key, ...$elements);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function lPushx(string $key, mixed $value);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function rPushx(string $key, mixed $value);
|
||||
|
||||
public function lSet(string $key, int $index, mixed $value): Redis|bool;
|
||||
|
||||
public function lastSave(): int;
|
||||
|
||||
public function lindex(string $key, int $index): Redis|mixed|false;
|
||||
|
||||
public function lrange(string $key, int $start , int $end): Redis|array|false;
|
||||
|
||||
/**
|
||||
* @return int|Redis|false
|
||||
*/
|
||||
public function lrem(string $key, mixed $value, int $count = 0);
|
||||
|
||||
public function ltrim(string $key, int $start , int $end): Redis|bool;
|
||||
|
||||
/** @return array|Redis */
|
||||
public function mget(array $keys);
|
||||
|
||||
public function migrate(string $host, int $port, string $key, string $dst, int $timeout, bool $copy = false, bool $replace = false): bool;
|
||||
|
||||
public function move(string $key, int $index): bool;
|
||||
|
||||
public function mset(array $key_values): Redis|bool;
|
||||
|
||||
public function msetnx(array $key_values): Redis|bool;
|
||||
|
||||
public function multi(int $value = Redis::MULTI): bool|Redis;
|
||||
|
||||
public function object(string $subcommand, string $key): Redis|int|string|false;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @alias Redis::connect
|
||||
*/
|
||||
public function open(string $host, int $port = 26379, float $timeout = 0, string $persistent_id = NULL, int $retry_interval = 0, float $read_timeout = 0, array $context = NULL): bool;
|
||||
|
||||
public function pconnect(string $host, int $port = 26379, float $timeout = 0, string $persistent_id = NULL, int $retry_interval = 0, float $read_timeout = 0, array $context = NULL): bool;
|
||||
|
||||
public function persist(string $key): bool;
|
||||
|
||||
public function pexpire(string $key, int $timeout): bool;
|
||||
|
||||
public function pexpireAt(string $key, int $timestamp): bool;
|
||||
|
||||
public function pfadd(string $key, array $elements): int;
|
||||
|
||||
public function pfcount(string $key): int;
|
||||
|
||||
public function pfmerge(string $dst, array $keys): bool;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function ping(string $key = NULL);
|
||||
|
||||
public function pipeline(): bool|Redis;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @alias Redis::pconnect
|
||||
*/
|
||||
public function popen(string $host, int $port = 26379, float $timeout = 0, string $persistent_id = NULL, int $retry_interval = 0, float $read_timeout = 0, array $context = NULL): bool;
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function psetex(string $key, int $expire, mixed $value);
|
||||
|
||||
public function psubscribe(array $patterns): void;
|
||||
|
||||
public function pttl(string $key): Redis|int|false;
|
||||
|
||||
public function publish(string $channel, string $message): mixed;
|
||||
|
||||
public function pubsub(string $command, mixed $arg = null): mixed;
|
||||
|
||||
public function punsubscribe(array $patterns): array;
|
||||
|
||||
/** @return string|Redis */
|
||||
public function rPop(string $key);
|
||||
|
||||
/** @return string|Redis */
|
||||
public function randomKey();
|
||||
|
||||
public function rawcommand(string $command, mixed ...$args): mixed;
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function rename(string $key_src, string $key_dst);
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function renameNx(string $key_src, string $key_dst);
|
||||
|
||||
public function restore(string $key, int $timeout, string $value): bool;
|
||||
|
||||
public function role(): mixed;
|
||||
|
||||
public function rpoplpush(string $src, string $dst): Redis|string|false;
|
||||
|
||||
public function sAdd(string $key, mixed $value, mixed ...$other_values): Redis|int|false;
|
||||
|
||||
public function sAddArray(string $key, array $values): int;
|
||||
|
||||
public function sDiff(string $key, string ...$other_keys): Redis|array|false;
|
||||
|
||||
public function sDiffStore(string $dst, string $key, string ...$other_keys): Redis|int|false;
|
||||
|
||||
public function sInter(array|string $key, string ...$other_keys): Redis|array|false;
|
||||
|
||||
public function sInterStore(array|string $key, string ...$other_keys): Redis|int|false;
|
||||
|
||||
public function sMembers(string $key): Redis|array|false;
|
||||
|
||||
public function sMisMember(string $key, string $member, string ...$other_members): array;
|
||||
|
||||
public function sMove(string $src, string $dst, mixed $value): Redis|bool;
|
||||
|
||||
public function sPop(string $key, int $count = 0): Redis|string|array|false;
|
||||
|
||||
public function sRandMember(string $key, int $count = 0): Redis|string|array|false;
|
||||
|
||||
public function sUnion(string $key, string ...$other_keys): Redis|array|false;
|
||||
|
||||
public function sUnionStore(string $dst, string $key, string ...$other_keys): Redis|int|false;
|
||||
|
||||
public function save(): bool;
|
||||
|
||||
public function scan(?int &$iterator, ?string $pattern = null, int $count = 0, string $type = NULL): array|false;
|
||||
|
||||
public function scard(string $key): Redis|int|false;
|
||||
|
||||
public function script(string $command, mixed ...$args): mixed;
|
||||
|
||||
public function select(int $db): bool;
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function set(string $key, mixed $value, mixed $opt = NULL);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function setBit(string $key, int $idx, bool $value);
|
||||
|
||||
/** @return int|Redis */
|
||||
public function setRange(string $key, int $start, string $value);
|
||||
|
||||
|
||||
public function setOption(string $option, mixed $value): bool;
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function setex(string $key, int $expire, mixed $value);
|
||||
|
||||
/** @return bool|array|Redis */
|
||||
public function setnx(string $key, mixed $value);
|
||||
|
||||
public function sismember(string $key, mixed $value): Redis|bool;
|
||||
|
||||
public function slaveof(string $host = null, int $port = 6379): bool;
|
||||
|
||||
public function slowlog(string $mode, int $option = 0): mixed;
|
||||
|
||||
public function sort(string $key, array $options = null): mixed;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function sortAsc(string $key, ?string $pattern = null, mixed $get = null, int $offset = -1, int $count = -1, ?string $store = null): array;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function sortAscAlpha(string $key, ?string $pattern = null, mixed $get = null, int $offset = -1, int $count = -1, ?string $store = null): array;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function sortDesc(string $key, ?string $pattern = null, mixed $get = null, int $offset = -1, int $count = -1, ?string $store = null): array;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function sortDescAlpha(string $key, ?string $pattern = null, mixed $get = null, int $offset = -1, int $count = -1, ?string $store = null): array;
|
||||
|
||||
public function srem(string $key, mixed $value, mixed ...$other_values): Redis|int|false;
|
||||
|
||||
public function sscan(string $key, int &$iterator, ?string $pattern = null, int $count = 0): array|false;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function strlen(string $key);
|
||||
|
||||
public function subscribe(string $channel, string ...$other_channels): array;
|
||||
|
||||
public function swapdb(string $src, string $dst): bool;
|
||||
|
||||
public function time(): array;
|
||||
|
||||
public function ttl(string $key): Redis|int|false;
|
||||
|
||||
/** @return int|Redis */
|
||||
public function type(string $key);
|
||||
|
||||
/**
|
||||
* @return int|Redis
|
||||
*/
|
||||
public function unlink(array|string $key, string ...$other_keys);
|
||||
|
||||
public function unsubscribe(string $channel, string ...$other_channels): array;
|
||||
|
||||
/** @return bool|Redis */
|
||||
public function unwatch();
|
||||
|
||||
/**
|
||||
* @return bool|Redis
|
||||
*/
|
||||
public function watch(array|string $key, string ...$other_keys);
|
||||
|
||||
public function wait(int $count, int $timeout): int|false;
|
||||
|
||||
public function xack(string $key, string $group, array $ids): int|false;
|
||||
|
||||
public function xadd(string $key, string $id, array $values, int $maxlen = 0, bool $approx = false): string|false;
|
||||
|
||||
public function xclaim(string $key, string $group, string $consumer, int $min_iddle, array $ids, array $options): string|array;
|
||||
|
||||
public function xdel(string $key, array $ids): Redis|int|false;
|
||||
|
||||
public function xgroup(string $operation, string $key = null, string $arg1 = null, string $arg2 = null, bool $arg3 = false): mixed;
|
||||
|
||||
public function xinfo(string $operation, ?string $arg1 = null, ?string $arg2 = null, int $count = -1): mixed;
|
||||
|
||||
public function xlen(string $key): int;
|
||||
|
||||
public function xpending(string $key, string $group, string $start = null, string $end = null, int $count = -1, string $consumer = null): Redis|array|false;
|
||||
|
||||
public function xrange(string $key, string $start, string $end, int $count = -1): bool|array;
|
||||
|
||||
public function xread(array $streams, int $count = -1, int $block = -1): bool|array;
|
||||
|
||||
public function xreadgroup(string $group, string $consumer, array $streams, int $count = 1, int $block = 1): bool|array;
|
||||
|
||||
public function xrevrange(string $key, string $start, string $end, int $count = -1): bool|array;
|
||||
|
||||
public function xtrim(string $key, int $maxlen, bool $approx = false): int;
|
||||
|
||||
public function zAdd(string $key, array|float $score_or_options, mixed ...$more_scores_and_mems): Redis|int|false;
|
||||
|
||||
public function zCard(string $key): Redis|int|false;
|
||||
|
||||
public function zCount(string $key, string $start , string $end): Redis|int|false;
|
||||
|
||||
public function zIncrBy(string $key, float $value, mixed $member): Redis|float|false;
|
||||
|
||||
public function zLexCount(string $key, string $min, string $max): Redis|int|false;
|
||||
|
||||
public function zMscore(string $key, string $member, string ...$other_members): array;
|
||||
|
||||
public function zPopMax(string $key, int $value = null): array;
|
||||
|
||||
public function zPopMin(string $key, int $value = null): array;
|
||||
|
||||
public function zRange(string $key, int $start, int $end, mixed $scores = null): Redis|array|false;
|
||||
|
||||
public function zRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array;
|
||||
|
||||
public function zRangeByScore(string $key, string $start, string $end, array $options = []): Redis|array|false;
|
||||
|
||||
public function zRank(string $key, mixed $member): Redis|int|false;
|
||||
|
||||
public function zRem(mixed $key, mixed $member, mixed ...$other_members): Redis|int|false;
|
||||
|
||||
public function zRemRangeByLex(string $key, string $min, string $max): int;
|
||||
|
||||
public function zRemRangeByRank(string $key, int $start, int $end): Redis|int|false;
|
||||
|
||||
public function zRemRangeByScore(string $key, string $start, string $end): Redis|int|false;
|
||||
|
||||
public function zRevRange(string $key, int $start, int $end, mixed $scores = null): Redis|array|false;
|
||||
|
||||
public function zRevRangeByLex(string $key, string $min, string $max, int $offset = -1, int $count = -1): array;
|
||||
|
||||
public function zRevRangeByScore(string $key, string $start, string $end, array $options = []): array;
|
||||
|
||||
public function zRevRank(string $key, mixed $member): Redis|int|false;
|
||||
|
||||
public function zScore(string $key, mixed $member): Redis|float|false;
|
||||
|
||||
public function zdiff(array $keys, array $options = null): array;
|
||||
|
||||
public function zdiffstore(string $dst, array $keys, array $options = null): int;
|
||||
|
||||
public function zinter(array $keys, ?array $weights = null, ?array $options = null): Redis|array|false;
|
||||
|
||||
public function zinterstore(string $dst, array $keys, ?array $weights = null, ?string $aggregate = null): Redis|int|false;
|
||||
|
||||
public function zscan(string $key, int &$iterator, ?string $pattern = null, int $count = 0): bool|array;
|
||||
|
||||
public function zunion(array $keys, ?array $weights = null, ?array $options = null): Redis|array|false;
|
||||
|
||||
public function zunionstore(string $dst, array $keys, ?array $weights = NULL, ?string $aggregate = NULL): Redis|int|false;
|
||||
}
|
@ -174,9 +174,7 @@ class ConfigFileTest extends TestCase
|
||||
{
|
||||
$noPlugins = trim('
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="false"
|
||||
>
|
||||
<psalm>
|
||||
<plugins>
|
||||
<pluginClass class="d\e\f"/>
|
||||
</plugins>
|
||||
@ -185,9 +183,7 @@ class ConfigFileTest extends TestCase
|
||||
|
||||
$abcEnabled = trim('
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="false"
|
||||
>
|
||||
<psalm>
|
||||
<plugins>
|
||||
<pluginClass class="a\b\c"/>
|
||||
<pluginClass class="d\e\f"/>
|
||||
|
@ -355,6 +355,36 @@ class ConfigTest extends TestCase
|
||||
$this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Type.php')));
|
||||
}
|
||||
|
||||
public function testGlobalUndefinedFunctionSuppression(): void
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
Config::loadFromXML(
|
||||
dirname(__DIR__, 2),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<projectFiles>
|
||||
<directory name="src" />
|
||||
<directory name="tests" />
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<UndefinedFunction>
|
||||
<errorLevel type="suppress">
|
||||
<referencedFunction name="zzz"/>
|
||||
</errorLevel>
|
||||
</UndefinedFunction>
|
||||
</issueHandlers>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$config = $this->project_analyzer->getConfig();
|
||||
$this->assertSame(
|
||||
Config::REPORT_SUPPRESS,
|
||||
$config->getReportingLevelForFunction('UndefinedFunction', 'Some\Namespace\zzz')
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleIssueHandlers(): void
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
|
@ -113,8 +113,10 @@ class IssueBufferTest extends TestCase
|
||||
|
||||
public function testPrintSuccessMessageWorks(): void
|
||||
{
|
||||
$project_analyzer = $this->createMock(ProjectAnalyzer::class);
|
||||
$project_analyzer->stdout_report_options = new ReportOptions;
|
||||
ob_start();
|
||||
IssueBuffer::printSuccessMessage();
|
||||
IssueBuffer::printSuccessMessage($project_analyzer);
|
||||
$output = ob_get_clean();
|
||||
|
||||
$this->assertStringContainsString('No errors found!', $output);
|
||||
|
@ -2285,6 +2285,25 @@ class TaintTest extends TestCase
|
||||
echo sinkNotWorking($_GET["taint"]);',
|
||||
'error_message' => 'TaintedHtml',
|
||||
],
|
||||
'taintEscapedInTryMightNotWork' => [
|
||||
'<?php
|
||||
/** @psalm-taint-escape html */
|
||||
function escapeHtml(string $arg): string
|
||||
{
|
||||
return htmlspecialchars($arg);
|
||||
}
|
||||
|
||||
$tainted = $_GET["foo"];
|
||||
|
||||
try {
|
||||
$tainted = escapeHtml($tainted);
|
||||
} catch (Throwable $_) {
|
||||
}
|
||||
|
||||
echo $tainted;
|
||||
',
|
||||
'error_message' => 'TaintedHtml',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -2436,6 +2436,43 @@ class UnusedVariableTest extends TestCase
|
||||
$a = false;
|
||||
}'
|
||||
],
|
||||
'usedInCatchIsAlwaysUsedInTry' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
$step = 1;
|
||||
$step = 2;
|
||||
} catch (Throwable $_) {
|
||||
echo $step;
|
||||
}
|
||||
',
|
||||
],
|
||||
'usedInFinallyIsAlwaysUsedInTry' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
$step = 1;
|
||||
$step = 2;
|
||||
} finally {
|
||||
echo $step;
|
||||
}
|
||||
',
|
||||
],
|
||||
'usedInFinallyIsAlwaysUsedInTryWithNestedTry' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
try {
|
||||
$step = 1;
|
||||
} finally {
|
||||
}
|
||||
$step = 2;
|
||||
$step = 3;
|
||||
} finally {
|
||||
echo $step;
|
||||
}
|
||||
',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -3416,6 +3453,49 @@ class UnusedVariableTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'MixedArgumentTypeCoercion - src' . DIRECTORY_SEPARATOR . 'somefile.php:12:44 - Argument 1 of takesArrayOfString expects array<array-key, string>, parent type array{mixed} provided. Consider improving the type at src' . DIRECTORY_SEPARATOR . 'somefile.php:10:41'
|
||||
],
|
||||
'warnAboutUnusedVariableInTryReassignedInCatch' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
$step = 1;
|
||||
$step = 2;
|
||||
} catch (Throwable $_) {
|
||||
$step = 3;
|
||||
echo $step;
|
||||
}
|
||||
',
|
||||
'error_message' => 'UnusedVariable',
|
||||
],
|
||||
'warnAboutUnusedVariableInTryReassignedInFinally' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
$step = 1;
|
||||
$step = 2;
|
||||
} finally {
|
||||
$step = 3;
|
||||
echo $step;
|
||||
}
|
||||
',
|
||||
'error_message' => 'UnusedVariable',
|
||||
],
|
||||
'SKIPPED-warnAboutVariableUsedInNestedTryNotUsedInOuterTry' => [
|
||||
'<?php
|
||||
$step = 0;
|
||||
try {
|
||||
$step = 1; // Unused
|
||||
$step = 2;
|
||||
try {
|
||||
$step = 3;
|
||||
$step = 4;
|
||||
} finally {
|
||||
echo $step;
|
||||
}
|
||||
} finally {
|
||||
}
|
||||
',
|
||||
'error_message' => 'UnusedVariable',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="true"
|
||||
autoloader="autoloader.php"
|
||||
resolveFromConfigFile="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
1
tests/fixtures/DummyProject/psalm.xml
vendored
1
tests/fixtures/DummyProject/psalm.xml
vendored
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
|
1
tests/fixtures/ModularConfig/psalm.xml
vendored
1
tests/fixtures/ModularConfig/psalm.xml
vendored
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
|
1
tests/fixtures/SuicidalAutoloader/psalm.xml
vendored
1
tests/fixtures/SuicidalAutoloader/psalm.xml
vendored
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
totallyTyped="true"
|
||||
autoloader="autoloader.php"
|
||||
resolveFromConfigFile="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
Loading…
Reference in New Issue
Block a user