1
0
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:
Joram Schrijver 2019-11-08 13:01:34 +01:00 committed by Matthew Brown
parent e79a0cc8f0
commit c97ba8f713
4 changed files with 129 additions and 4 deletions

View File

@ -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(

View File

@ -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

View File

@ -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
*/

View File

@ -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',
],
];
}
}