1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

create a separate issue type

This commit is contained in:
kkmuffme 2024-01-12 22:52:26 +01:00
parent f3543ca9ab
commit fb93aede12
9 changed files with 62 additions and 12 deletions

View File

@ -427,6 +427,7 @@
<xs:element name="ReferenceReusedFromConfusingScope" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ReferenceReusedFromConfusingScope" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ReservedWord" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ReservedWord" type="IssueHandlerType" minOccurs="0" />
<xs:element name="RiskyCast" type="IssueHandlerType" minOccurs="0" /> <xs:element name="RiskyCast" type="IssueHandlerType" minOccurs="0" />
<xs:element name="RiskyTruthyFalsyComparison" type="IssueHandlerType" minOccurs="0" />
<xs:element name="StringIncrement" type="IssueHandlerType" minOccurs="0" /> <xs:element name="StringIncrement" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedCallable" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedCallable" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedCookie" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedCookie" type="IssueHandlerType" minOccurs="0" />

View File

@ -173,6 +173,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even
- [TooManyArguments](issues/TooManyArguments.md) - [TooManyArguments](issues/TooManyArguments.md)
- [TypeDoesNotContainNull](issues/TypeDoesNotContainNull.md) - [TypeDoesNotContainNull](issues/TypeDoesNotContainNull.md)
- [TypeDoesNotContainType](issues/TypeDoesNotContainType.md) - [TypeDoesNotContainType](issues/TypeDoesNotContainType.md)
- [RiskyTruthyFalsyComparison](issues/RiskyTruthyFalsyComparison.md)
- [UndefinedMagicMethod](issues/UndefinedMagicMethod.md) - [UndefinedMagicMethod](issues/UndefinedMagicMethod.md)
- [UndefinedMagicPropertyAssignment](issues/UndefinedMagicPropertyAssignment.md) - [UndefinedMagicPropertyAssignment](issues/UndefinedMagicPropertyAssignment.md)
- [UndefinedMagicPropertyFetch](issues/UndefinedMagicPropertyFetch.md) - [UndefinedMagicPropertyFetch](issues/UndefinedMagicPropertyFetch.md)

View File

@ -229,6 +229,7 @@
- [ReferenceReusedFromConfusingScope](issues/ReferenceReusedFromConfusingScope.md) - [ReferenceReusedFromConfusingScope](issues/ReferenceReusedFromConfusingScope.md)
- [ReservedWord](issues/ReservedWord.md) - [ReservedWord](issues/ReservedWord.md)
- [RiskyCast](issues/RiskyCast.md) - [RiskyCast](issues/RiskyCast.md)
- [RiskyTruthyFalsyComparison](issues/RiskyTruthyFalsyComparison.md)
- [StringIncrement](issues/StringIncrement.md) - [StringIncrement](issues/StringIncrement.md)
- [TaintedCallable](issues/TaintedCallable.md) - [TaintedCallable](issues/TaintedCallable.md)
- [TaintedCookie](issues/TaintedCookie.md) - [TaintedCookie](issues/TaintedCookie.md)

View File

@ -0,0 +1,29 @@
# RiskyTruthyFalsyComparison
Emitted when comparing a value with multiple types that can both contain truthy and falsy values.
```php
<?php
/**
* @param array|null $arg
* @return void
*/
function foo($arg) {
if ($arg) {
// this is risky, bc the empty array and null case are handled together
}
if (!$arg) {
// this is risky, bc the empty array and null case are handled together
}
}
```
## Why this is bad
The truthy/falsy type of one variable is often forgotten and not handled explicitly causing hard to track down errors.
## How to fix
Explicitly validate the variable with strict comparison.

View File

@ -15,6 +15,7 @@ use Psalm\Internal\Scope\IfScope;
use Psalm\Issue\DocblockTypeContradiction; use Psalm\Issue\DocblockTypeContradiction;
use Psalm\Issue\RedundantCondition; use Psalm\Issue\RedundantCondition;
use Psalm\Issue\RedundantConditionGivenDocblockType; use Psalm\Issue\RedundantConditionGivenDocblockType;
use Psalm\Issue\RiskyTruthyFalsyComparison;
use Psalm\Issue\TypeDoesNotContainType; use Psalm\Issue\TypeDoesNotContainType;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TBool;
@ -388,13 +389,13 @@ final class IfConditionalAnalyzer
if ($has_both) { if ($has_both) {
$both_types = $both_types->freeze(); $both_types = $both_types->freeze();
IssueBuffer::maybeAdd( IssueBuffer::maybeAdd(
new TypeDoesNotContainType( new RiskyTruthyFalsyComparison(
'Operand of type ' . $type->getId() . ' contains ' . 'Operand of type ' . $type->getId() . ' contains ' .
'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' . 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
$both_types->getId() . ', which can be falsy and truthy. ' . $both_types->getId() . ', which can be falsy and truthy. ' .
'This can cause possibly unexpected behavior. Use strict comparison instead.', 'This can cause possibly unexpected behavior. Use strict comparison instead.',
new CodeLocation($statements_analyzer, $stmt), new CodeLocation($statements_analyzer, $stmt),
$type->getId() . ' truthy-falsy', $type->getId(),
), ),
$statements_analyzer->getSuppressedIssues(), $statements_analyzer->getSuppressedIssues(),
); );

View File

@ -7,7 +7,7 @@ use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer; use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Issue\TypeDoesNotContainType; use Psalm\Issue\RiskyTruthyFalsyComparison;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Type; use Psalm\Type;
use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TBool;
@ -63,13 +63,13 @@ final class BooleanNotAnalyzer
if ($has_both) { if ($has_both) {
$both_types = $both_types->freeze(); $both_types = $both_types->freeze();
IssueBuffer::maybeAdd( IssueBuffer::maybeAdd(
new TypeDoesNotContainType( new RiskyTruthyFalsyComparison(
'Operand of type ' . $expr_type->getId() . ' contains ' . 'Operand of type ' . $expr_type->getId() . ' contains ' .
'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' . 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
$both_types->getId() . ', which can be falsy and truthy. ' . $both_types->getId() . ', which can be falsy and truthy. ' .
'This can cause possibly unexpected behavior. Use strict comparison instead.', 'This can cause possibly unexpected behavior. Use strict comparison instead.',
new CodeLocation($statements_analyzer, $stmt), new CodeLocation($statements_analyzer, $stmt),
$expr_type->getId() . ' truthy-falsy', $expr_type->getId(),
), ),
$statements_analyzer->getSuppressedIssues(), $statements_analyzer->getSuppressedIssues(),
); );

View File

@ -8,7 +8,7 @@ use Psalm\Context;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Issue\ForbiddenCode; use Psalm\Issue\ForbiddenCode;
use Psalm\Issue\InvalidArgument; use Psalm\Issue\InvalidArgument;
use Psalm\Issue\TypeDoesNotContainType; use Psalm\Issue\RiskyTruthyFalsyComparison;
use Psalm\IssueBuffer; use Psalm\IssueBuffer;
use Psalm\Type; use Psalm\Type;
use Psalm\Type\Atomic\TBool; use Psalm\Type\Atomic\TBool;
@ -82,13 +82,13 @@ final class EmptyAnalyzer
if ($has_both) { if ($has_both) {
$both_types = $both_types->freeze(); $both_types = $both_types->freeze();
IssueBuffer::maybeAdd( IssueBuffer::maybeAdd(
new TypeDoesNotContainType( new RiskyTruthyFalsyComparison(
'Operand of type ' . $expr_type->getId() . ' contains ' . 'Operand of type ' . $expr_type->getId() . ' contains ' .
'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' . 'type' . (count($both_types->getAtomicTypes()) > 1 ? 's' : '') . ' ' .
$both_types->getId() . ', which can be falsy and truthy. ' . $both_types->getId() . ', which can be falsy and truthy. ' .
'This can cause possibly unexpected behavior. Use strict comparison instead.', 'This can cause possibly unexpected behavior. Use strict comparison instead.',
new CodeLocation($statements_analyzer, $stmt), new CodeLocation($statements_analyzer, $stmt),
$expr_type->getId() . ' truthy-falsy', $expr_type->getId(),
), ),
$statements_analyzer->getSuppressedIssues(), $statements_analyzer->getSuppressedIssues(),
); );

View File

@ -0,0 +1,17 @@
<?php
namespace Psalm\Issue;
use Psalm\CodeLocation;
final class RiskyTruthyFalsyComparison extends CodeIssue
{
public const ERROR_LEVEL = 4;
public const SHORTCODE = 356;
public function __construct(string $message, CodeLocation $code_location, ?string $dupe_key)
{
parent::__construct($message, $code_location);
$this->dupe_key = $dupe_key;
}
}

View File

@ -3528,7 +3528,7 @@ class ConditionalTest extends TestCase
if ($arg) { if ($arg) {
} }
}', }',
'error_message' => 'TypeDoesNotContainType', 'error_message' => 'RiskyTruthyFalsyComparison',
], ],
'nonStrictConditionTruthyFalsyNegated' => [ 'nonStrictConditionTruthyFalsyNegated' => [
'code' => '<?php 'code' => '<?php
@ -3540,7 +3540,7 @@ class ConditionalTest extends TestCase
if (!$arg) { if (!$arg) {
} }
}', }',
'error_message' => 'TypeDoesNotContainType', 'error_message' => 'RiskyTruthyFalsyComparison',
], ],
'nonStrictConditionTruthyFalsyFuncCall' => [ 'nonStrictConditionTruthyFalsyFuncCall' => [
'code' => '<?php 'code' => '<?php
@ -3558,7 +3558,7 @@ class ConditionalTest extends TestCase
* @return array|null * @return array|null
*/ */
function bar($arg) {}', function bar($arg) {}',
'error_message' => 'TypeDoesNotContainType', 'error_message' => 'RiskyTruthyFalsyComparison',
], ],
'nonStrictConditionTruthyFalsyFuncCallNegated' => [ 'nonStrictConditionTruthyFalsyFuncCallNegated' => [
'code' => '<?php 'code' => '<?php
@ -3576,7 +3576,7 @@ class ConditionalTest extends TestCase
* @return array|null * @return array|null
*/ */
function bar($arg) {}', function bar($arg) {}',
'error_message' => 'TypeDoesNotContainType', 'error_message' => 'RiskyTruthyFalsyComparison',
], ],
'redundantConditionForNonEmptyString' => [ 'redundantConditionForNonEmptyString' => [
'code' => '<?php 'code' => '<?php