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:
parent
f3543ca9ab
commit
fb93aede12
@ -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" />
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
29
docs/running_psalm/issues/RiskyTruthyFalsyComparison.md
Normal file
29
docs/running_psalm/issues/RiskyTruthyFalsyComparison.md
Normal 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.
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
17
src/Psalm/Issue/RiskyTruthyFalsyComparison.php
Normal file
17
src/Psalm/Issue/RiskyTruthyFalsyComparison.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user