mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Add new annotation: @psalm-self-out (#3650)
* Add new config: sealAllMethods * Add some more tests * Fix codesniffer issue with preg_quote * Fix missing method in test * New tag @self-out (WIP) * Add self_out_type to method storage * Add some notes * More work on self-out (WIP) * More work on self-out (WIP) * Use psalm-self-out instead of self-out * Remove extra file * Cleanup * Wrap around try-catch - how to check if a method has/should have storage? * New method hasStorage() * Fix indentation * Fix some errors * Fix indentation * Cast storage type to type * Add proper use-statement in method storage * Correct test class name * Allow self_out to be null * method_id can be string (why, when?) Co-authored-by: Olle <noemail>
This commit is contained in:
parent
b0a3de47e8
commit
d8e8ce428e
@ -35,7 +35,7 @@ class DocComment
|
||||
'mutation-free', 'external-mutation-free', 'immutable', 'readonly',
|
||||
'allow-private-mutation', 'readonly-allow-private-mutation',
|
||||
'yield', 'trace', 'import-type', 'flow', 'taint-specialize', 'taint-escape',
|
||||
'taint-unescape'
|
||||
'taint-unescape', 'self-out'
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -467,6 +467,21 @@ class CommentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock->tags['psalm-self-out'])) {
|
||||
foreach ($parsed_docblock->tags['psalm-self-out'] as $offset => $param) {
|
||||
$line_parts = self::splitDocLine($param);
|
||||
|
||||
if (count($line_parts) > 0) {
|
||||
$line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0]));
|
||||
|
||||
$info->self_out = [
|
||||
'type' => str_replace("\n", '', $line_parts[0]),
|
||||
'line_number' => $comment->getLine() + substr_count($comment_text, "\n", 0, $offset),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock->tags['psalm-flow'])) {
|
||||
foreach ($parsed_docblock->tags['psalm-flow'] as $param) {
|
||||
$info->flows[] = trim($param);
|
||||
|
@ -5,6 +5,7 @@ use PhpParser;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\MethodIdentifier;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\InvalidMethodCall;
|
||||
@ -404,6 +405,23 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
|
||||
$context->vars_in_scope[$lhs_var_id] = $class_type;
|
||||
}
|
||||
|
||||
if ($lhs_var_id) {
|
||||
// TODO: Always defined? Always correct?
|
||||
$method_id = $result->existent_method_ids[0];
|
||||
if ($method_id instanceof MethodIdentifier) {
|
||||
// TODO: When should a method have a storage?
|
||||
if ($codebase->methods->hasStorage($method_id)) {
|
||||
$storage = $codebase->methods->getStorage($method_id);
|
||||
if ($storage->self_out_type) {
|
||||
$self_out_type = $storage->self_out_type;
|
||||
$context->vars_in_scope[$lhs_var_id] = $self_out_type;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: When is method_id a string?
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1057,4 +1057,24 @@ class Methods
|
||||
|
||||
return $class_storage->methods[$method_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStorage(MethodIdentifier $method_id)
|
||||
{
|
||||
try {
|
||||
$class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$method_name = $method_id->method_name;
|
||||
|
||||
if (!isset($class_storage->methods[$method_name])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -2503,6 +2503,22 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
}
|
||||
}
|
||||
|
||||
if ($docblock_info->self_out
|
||||
&& $storage instanceof MethodStorage) {
|
||||
$out_type = TypeParser::parseTokens(
|
||||
TypeTokenizer::getFullyQualifiedTokens(
|
||||
$docblock_info->self_out['type'],
|
||||
$this->aliases,
|
||||
$this->function_template_types + $class_template_types,
|
||||
$this->type_aliases
|
||||
),
|
||||
null,
|
||||
$this->function_template_types + $class_template_types,
|
||||
$this->type_aliases
|
||||
);
|
||||
$storage->self_out_type = $out_type;
|
||||
}
|
||||
|
||||
foreach ($docblock_info->taint_sink_params as $taint_sink_param) {
|
||||
$param_name = substr($taint_sink_param['name'], 1);
|
||||
|
||||
|
@ -41,6 +41,11 @@ class FunctionDocblockComment
|
||||
*/
|
||||
public $params_out = [];
|
||||
|
||||
/**
|
||||
* @var array{type:string, line_number: int}|null
|
||||
*/
|
||||
public $self_out;
|
||||
|
||||
/**
|
||||
* @var array<int, array{name:string, type:string, line_number: int}>
|
||||
*/
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace Psalm\Storage;
|
||||
|
||||
use Psalm\Type;
|
||||
|
||||
class MethodStorage extends FunctionLikeStorage
|
||||
{
|
||||
/**
|
||||
@ -72,4 +74,9 @@ class MethodStorage extends FunctionLikeStorage
|
||||
* @var ?string
|
||||
*/
|
||||
public $plain_getter = null;
|
||||
|
||||
/**
|
||||
* @var Type\Union|null
|
||||
*/
|
||||
public $self_out_type = null;
|
||||
}
|
||||
|
38
tests/SelfOutTest.php
Normal file
38
tests/SelfOutTest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace Psalm\Tests;
|
||||
|
||||
class SelfOutTest extends TestCase
|
||||
{
|
||||
use Traits\ValidCodeAnalysisTestTrait;
|
||||
|
||||
/**
|
||||
* @return iterable<string,array{string,assertions?:array<string,string>,error_levels?:string[]}>
|
||||
*/
|
||||
public function providerValidCodeParse()
|
||||
{
|
||||
return [
|
||||
'changeInterface' => [
|
||||
'<?php
|
||||
interface Foo {
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function far() {
|
||||
}
|
||||
}
|
||||
class Bar {
|
||||
/**
|
||||
* @psalm-self-out Foo
|
||||
* @return void
|
||||
*/
|
||||
public function baz() {
|
||||
}
|
||||
}
|
||||
$bar = new Bar();
|
||||
$bar->baz();
|
||||
$bar->far();
|
||||
'
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user