mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Implement type-checking and forbidding for print (#2314)
* Add type-checking for print * Allow print to be forbidden
This commit is contained in:
parent
e79a0cc8f0
commit
c97ba8f713
@ -2446,6 +2446,7 @@ class CallAnalyzer
|
||||
if (!$function_param->by_ref
|
||||
&& !($function_param->is_variadic xor $unpack)
|
||||
&& $cased_method_id !== 'echo'
|
||||
&& $cased_method_id !== 'print'
|
||||
&& (!$in_call_map || $context->strict_types)
|
||||
) {
|
||||
self::coerceValueAfterGatekeeperArgument(
|
||||
@ -2584,6 +2585,7 @@ class CallAnalyzer
|
||||
&& !$input_type->hasArray()
|
||||
&& !$param_type->from_docblock
|
||||
&& $cased_method_id !== 'echo'
|
||||
&& $cased_method_id !== 'print'
|
||||
&& $cased_method_id !== 'sprintf'
|
||||
) {
|
||||
$union_comparison_results->scalar_type_match_found = false;
|
||||
@ -2622,7 +2624,7 @@ class CallAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($union_comparison_results->to_string_cast && $cased_method_id !== 'echo') {
|
||||
if ($union_comparison_results->to_string_cast && $cased_method_id !== 'echo' && $cased_method_id !== 'print') {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImplicitToStringCast(
|
||||
'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' .
|
||||
@ -2645,7 +2647,7 @@ class CallAnalyzer
|
||||
);
|
||||
|
||||
if ($union_comparison_results->scalar_type_match_found) {
|
||||
if ($cased_method_id !== 'echo') {
|
||||
if ($cased_method_id !== 'echo' && $cased_method_id !== 'print') {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidScalarArgument(
|
||||
'Argument ' . ($argument_offset + 1) . $method_identifier . ' expects ' .
|
||||
@ -2809,7 +2811,7 @@ class CallAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if (!$param_type->isNullable() && $cased_method_id !== 'echo') {
|
||||
if (!$param_type->isNullable() && $cased_method_id !== 'echo' && $cased_method_id !== 'print') {
|
||||
if ($input_type->isNull()) {
|
||||
if (IssueBuffer::accepts(
|
||||
new NullArgument(
|
||||
@ -2863,6 +2865,7 @@ class CallAnalyzer
|
||||
&& !$function_param->by_ref
|
||||
&& !($function_param->is_variadic xor $unpack)
|
||||
&& $cased_method_id !== 'echo'
|
||||
&& $cased_method_id !== 'print'
|
||||
&& (!$in_call_map || $context->strict_types)
|
||||
) {
|
||||
self::coerceValueAfterGatekeeperArgument(
|
||||
|
@ -12,6 +12,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ArrayAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\AssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOpAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\CallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\FunctionCallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\MethodCallAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Call\NewAnalyzer;
|
||||
@ -46,6 +47,7 @@ use Psalm\Issue\UndefinedVariable;
|
||||
use Psalm\Issue\UnnecessaryVarAnnotation;
|
||||
use Psalm\Issue\UnrecognizedExpression;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Storage\FunctionLikeParameter;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\ObjectLike;
|
||||
use Psalm\Type\Atomic\Scalar;
|
||||
@ -683,7 +685,7 @@ class ExpressionAnalyzer
|
||||
// continue
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Print_) {
|
||||
if (self::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
||||
if (self::analyzePrint($statements_analyzer, $stmt, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($stmt instanceof PhpParser\Node\Expr\Yield_) {
|
||||
@ -1423,6 +1425,62 @@ class ExpressionAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\Print_ $stmt
|
||||
* @param Context $context
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
protected static function analyzePrint(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\Print_ $stmt,
|
||||
Context $context
|
||||
) {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (self::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($stmt->expr->inferredType)) {
|
||||
if (CallAnalyzer::checkFunctionArgumentType(
|
||||
$statements_analyzer,
|
||||
$stmt->expr->inferredType,
|
||||
Type::getString(),
|
||||
null,
|
||||
'print',
|
||||
0,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt->expr),
|
||||
$stmt->expr,
|
||||
$context,
|
||||
new FunctionLikeParameter('var', false),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($codebase->config->forbidden_functions['print'])) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ForbiddenCode(
|
||||
'You have forbidden the use of print',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
$stmt->inferredType = Type::getInt(false, 1);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\Yield_ $stmt
|
||||
|
@ -912,6 +912,60 @@ class ConfigTest extends \Psalm\Tests\TestCase
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testAllowedPrintFunction()
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__, 2),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm></psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$file_path = getcwd() . '/src/somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
'<?php
|
||||
print "hello";'
|
||||
);
|
||||
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testForbiddenPrintFunction()
|
||||
{
|
||||
$this->expectExceptionMessage('ForbiddenCode');
|
||||
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__, 2),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<forbiddenFunctions>
|
||||
<function name="print" />
|
||||
</forbiddenFunctions>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$file_path = getcwd() . '/src/somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
'<?php
|
||||
print "hello";'
|
||||
);
|
||||
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
|
@ -2622,6 +2622,16 @@ class FunctionCallTest extends TestCase
|
||||
array_push();',
|
||||
'error_message' => 'TooFewArguments',
|
||||
],
|
||||
'printOnlyString' => [
|
||||
'<?php
|
||||
print [];',
|
||||
'error_message' => 'InvalidArgument',
|
||||
],
|
||||
'printReturns1' => [
|
||||
'<?php
|
||||
(print "test") === 2;',
|
||||
'error_message' => 'TypeDoesNotContainType',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user