mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Add initial support for fixing MissingParamType
This commit is contained in:
parent
36343b07ce
commit
db89b3cc3f
@ -265,6 +265,11 @@ class Context
|
||||
*/
|
||||
public $calling_method_id;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $infer_types = false;
|
||||
|
||||
/**
|
||||
* @param string|null $self
|
||||
*/
|
||||
|
@ -1258,6 +1258,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
|
||||
|
||||
$method_context = clone $class_context;
|
||||
$method_context->collect_exceptions = $config->check_for_throws_docblock;
|
||||
$method_context->infer_types = $codebase->infer_types_from_usage;
|
||||
|
||||
$method_analyzer->analyze(
|
||||
$method_context,
|
||||
|
@ -553,20 +553,28 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
&& $function_param->location
|
||||
&& !isset($implemented_docblock_param_types[$offset])
|
||||
) {
|
||||
$possible_type = null;
|
||||
if ($codebase->alter_code
|
||||
&& isset($project_analyzer->getIssuesToFix()['MissingParamType'])
|
||||
) {
|
||||
$possible_type = $context->possible_param_types[$function_param->name] ?? null;
|
||||
|
||||
if (isset($context->possible_param_types[$function_param->name])) {
|
||||
$possible_type = $context->possible_param_types[$function_param->name];
|
||||
if (!$possible_type || $possible_type->hasMixed() || $possible_type->isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$infer_text = $codebase->infer_types_from_usage
|
||||
? ', ' . ($possible_type ? 'should be ' . $possible_type : 'could not infer type')
|
||||
: '';
|
||||
self::addOrUpdateParamType(
|
||||
$project_analyzer,
|
||||
$function_param->name,
|
||||
$possible_type
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->function instanceof Closure) {
|
||||
IssueBuffer::accepts(
|
||||
new MissingClosureParamType(
|
||||
'Parameter $' . $function_param->name . ' has no provided type' . $infer_text,
|
||||
'Parameter $' . $function_param->name . ' has no provided type',
|
||||
$function_param->location
|
||||
),
|
||||
array_merge($this->suppressed_issues, $storage->suppressed_issues)
|
||||
@ -574,7 +582,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
} else {
|
||||
IssueBuffer::accepts(
|
||||
new MissingParamType(
|
||||
'Parameter $' . $function_param->name . ' has no provided type' . $infer_text,
|
||||
'Parameter $' . $function_param->name . ' has no provided type',
|
||||
$function_param->location
|
||||
),
|
||||
array_merge($this->suppressed_issues, $storage->suppressed_issues)
|
||||
|
@ -750,6 +750,7 @@ class ProjectAnalyzer
|
||||
$safe_types = false
|
||||
) {
|
||||
$this->codebase->alter_code = true;
|
||||
$this->codebase->infer_types_from_usage = true;
|
||||
$this->show_issues = false;
|
||||
$this->php_major_version = $php_major_version;
|
||||
$this->php_minor_version = $php_minor_version;
|
||||
|
@ -655,7 +655,7 @@ class IfAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->infer_types_from_usage) {
|
||||
if ($if_context->infer_types) {
|
||||
$if_scope->possible_param_types = $if_context->possible_param_types;
|
||||
}
|
||||
} else {
|
||||
@ -1167,7 +1167,7 @@ class IfAnalyzer
|
||||
$if_scope->reasonable_clauses = [];
|
||||
}
|
||||
|
||||
if ($codebase->infer_types_from_usage) {
|
||||
if ($elseif_context->infer_types) {
|
||||
$elseif_possible_param_types = $elseif_context->possible_param_types;
|
||||
|
||||
if ($if_scope->possible_param_types) {
|
||||
@ -1587,7 +1587,7 @@ class IfAnalyzer
|
||||
$outer_context->possibly_thrown_exceptions += $else_context->possibly_thrown_exceptions;
|
||||
}
|
||||
|
||||
if ($codebase->infer_types_from_usage) {
|
||||
if ($else_context->infer_types) {
|
||||
$else_possible_param_types = $else_context->possible_param_types;
|
||||
|
||||
if ($if_scope->possible_param_types) {
|
||||
|
@ -541,7 +541,7 @@ class BinaryOpAnalyzer
|
||||
) {
|
||||
$stmt->inferredType = Type::getBool();
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Div) {
|
||||
if ($codebase->infer_types_from_usage
|
||||
if ($context->infer_types
|
||||
&& isset($stmt->left->inferredType)
|
||||
&& isset($stmt->right->inferredType)
|
||||
&& ($stmt->left->inferredType->isMixed() || $stmt->right->inferredType->isMixed())
|
||||
@ -643,9 +643,9 @@ class BinaryOpAnalyzer
|
||||
$config = Config::getInstance();
|
||||
|
||||
if ($codebase
|
||||
&& $codebase->infer_types_from_usage
|
||||
&& $statements_source
|
||||
&& $context
|
||||
&& $context->infer_types
|
||||
&& $left_type
|
||||
&& $right_type
|
||||
&& ($left_type->isVanillaMixed() || $right_type->isVanillaMixed())
|
||||
@ -1188,7 +1188,7 @@ class BinaryOpAnalyzer
|
||||
$right_type = isset($right->inferredType) ? $right->inferredType : null;
|
||||
$config = Config::getInstance();
|
||||
|
||||
if ($codebase->infer_types_from_usage
|
||||
if ($context->infer_types
|
||||
&& $left_type
|
||||
&& $right_type
|
||||
&& ($left_type->isMixed() || $right_type->isMixed())
|
||||
|
@ -1761,7 +1761,7 @@ class CallAnalyzer
|
||||
|
||||
$method_identifier = $cased_method_id ? ' of ' . $cased_method_id : '';
|
||||
|
||||
if ($codebase->infer_types_from_usage && $input_expr->inferredType) {
|
||||
if ($context->infer_types && isset($input_expr->inferredType)) {
|
||||
$source_analyzer = $statements_analyzer->getSource();
|
||||
|
||||
if ($source_analyzer instanceof FunctionLikeAnalyzer) {
|
||||
|
@ -1308,11 +1308,9 @@ class ExpressionAnalyzer
|
||||
PhpParser\Node\Scalar\Encapsed $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$function_storage = null;
|
||||
|
||||
if ($codebase->infer_types_from_usage) {
|
||||
if ($context->infer_types) {
|
||||
$source_analyzer = $statements_analyzer->getSource();
|
||||
|
||||
if ($source_analyzer instanceof FunctionLikeAnalyzer) {
|
||||
|
@ -496,6 +496,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
$function_id = strtolower($stmt->name->name);
|
||||
$function_context = new Context($context->self);
|
||||
$config = Config::getInstance();
|
||||
$function_context->infer_types = $codebase->infer_types_from_usage;
|
||||
$function_context->collect_references = $codebase->collect_references;
|
||||
$function_context->collect_exceptions = $config->check_for_throws_docblock;
|
||||
$this->function_analyzers[$function_id]->analyze($function_context, $context);
|
||||
|
@ -252,7 +252,7 @@ class FunctionDocblockManipulator
|
||||
*/
|
||||
public function setReturnType($php_type, $new_type, $phpdoc_type, $is_php_compatible, $description)
|
||||
{
|
||||
$new_type = str_replace(['<mixed, mixed>', '<empty, empty>'], '', $new_type);
|
||||
$new_type = str_replace(['<mixed, mixed>', '<array-key, mixed>', '<empty, empty>'], '', $new_type);
|
||||
|
||||
$this->new_php_return_type = $php_type;
|
||||
$this->new_phpdoc_return_type = $phpdoc_type;
|
||||
@ -274,7 +274,7 @@ class FunctionDocblockManipulator
|
||||
*/
|
||||
public function setParamType($param_name, $php_type, $new_type, $phpdoc_type, $is_php_compatible)
|
||||
{
|
||||
$new_type = str_replace(['<mixed, mixed>', '<empty, empty>'], '', $new_type);
|
||||
$new_type = str_replace(['<mixed, mixed>', '<array-key, mixed>', '<empty, empty>'], '', $new_type);
|
||||
|
||||
if ($php_type) {
|
||||
$this->new_php_param_types[$param_name] = $php_type;
|
||||
@ -317,7 +317,7 @@ class FunctionDocblockManipulator
|
||||
}
|
||||
|
||||
if (!$found_in_params) {
|
||||
$parsed_docblock['specials']['params'][] = $new_param_block;
|
||||
$parsed_docblock['specials']['param'][] = $new_param_block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -961,64 +961,6 @@ class AnnotationTest extends TestCase
|
||||
}',
|
||||
'error_message' => 'MismatchingDocblockReturnType',
|
||||
],
|
||||
'noStringParamType' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo substr($a, 4, 2);
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be string',
|
||||
'error_levels' => ['MixedArgument'],
|
||||
],
|
||||
'noParamTypeButConcat' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a . "foo";
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be string',
|
||||
'error_levels' => ['MixedOperand'],
|
||||
],
|
||||
'noParamTypeButAddition' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a + 5;
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be int|float',
|
||||
'error_levels' => ['MixedOperand', 'MixedArgument'],
|
||||
],
|
||||
'noParamTypeButDivision' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a / 5;
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be int|float',
|
||||
'error_levels' => ['MixedOperand', 'MixedArgument'],
|
||||
],
|
||||
'noParamTypeButTemplatedString' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo "$a";
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be string',
|
||||
'error_levels' => ['MixedOperand'],
|
||||
],
|
||||
'noStringIntParamType' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
if (is_string($a)) {
|
||||
echo substr($a, 4, 2);
|
||||
} else {
|
||||
echo substr("hello", $a, 2);
|
||||
}
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:2 - Parameter $a has no provided type,'
|
||||
. ' should be int|string',
|
||||
'error_levels' => ['MixedArgument'],
|
||||
],
|
||||
'intParamTypeDefinedInParent' => [
|
||||
'<?php
|
||||
class A {
|
||||
@ -1031,32 +973,6 @@ class AnnotationTest extends TestCase
|
||||
'error_message' => 'MissingParamType',
|
||||
'error_levels' => ['MethodSignatureMismatch'],
|
||||
],
|
||||
'alreadyHasCheck' => [
|
||||
'<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
if (is_string($s)) takesString($s);
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:4 - Parameter $s has no provided type,'
|
||||
. ' could not infer',
|
||||
'error_levels' => ['MixedArgument'],
|
||||
],
|
||||
'isSetBeforeInferrence' => [
|
||||
'input' => '<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
/** @return mixed */
|
||||
function returnsMixed() {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
$s = returnsMixed();
|
||||
takesString($s);
|
||||
}',
|
||||
'error_message' => 'MissingParamType - src' . DIRECTORY_SEPARATOR . 'somefile.php:7 - Parameter $s has no provided type,'
|
||||
. ' could not infer',
|
||||
'error_levels' => ['MixedArgument', 'InvalidReturnType', 'MixedAssignment'],
|
||||
],
|
||||
'psalmInvalidVar' => [
|
||||
'<?php
|
||||
class A
|
||||
|
@ -1302,6 +1302,152 @@ class FileManipulationTest extends TestCase
|
||||
[],
|
||||
true,
|
||||
],
|
||||
'noStringParamType' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo substr($a, 4, 2);
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param string $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
echo substr($a, 4, 2);
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'noParamTypeButConcat' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a . "foo";
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param string $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
echo $a . "foo";
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'noParamTypeButAddition' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a + 5;
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param int|float $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
echo $a + 5;
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'noParamTypeButDivision' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo $a / 5;
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param int|float $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
echo $a / 5;
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'noParamTypeButTemplatedString' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
echo "$a";
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param string $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
echo "$a";
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'noStringIntParamType' => [
|
||||
'<?php
|
||||
function fooFoo($a): void {
|
||||
if (is_string($a)) {
|
||||
echo substr($a, 4, 2);
|
||||
} else {
|
||||
echo substr("hello", $a, 2);
|
||||
}
|
||||
}',
|
||||
'<?php
|
||||
/**
|
||||
* @param int|string $a
|
||||
*/
|
||||
function fooFoo($a): void {
|
||||
if (is_string($a)) {
|
||||
echo substr($a, 4, 2);
|
||||
} else {
|
||||
echo substr("hello", $a, 2);
|
||||
}
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'alreadyHasCheck' => [
|
||||
'<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
if (is_string($s)) takesString($s);
|
||||
}',
|
||||
'<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
if (is_string($s)) takesString($s);
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
'isSetBeforeInferrence' => [
|
||||
'<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
/** @return mixed */
|
||||
function returnsMixed() {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
$s = returnsMixed();
|
||||
takesString($s);
|
||||
}',
|
||||
'<?php
|
||||
function takesString(string $s): void {}
|
||||
|
||||
/** @return mixed */
|
||||
function returnsMixed() {}
|
||||
|
||||
function shouldTakeString($s): void {
|
||||
$s = returnsMixed();
|
||||
takesString($s);
|
||||
}',
|
||||
'7.1',
|
||||
['MissingParamType'],
|
||||
true,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user