Skip to content

Commit 35e0235

Browse files
committed
RequireNullSafeObjectOperatorSniff: Fixed false positive
1 parent 86b0531 commit 35e0235

File tree

6 files changed

+61
-15
lines changed

6 files changed

+61
-15
lines changed

SlevomatCodingStandard/Helpers/TernaryOperatorHelper.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use const T_DOUBLE_ARROW;
1616
use const T_EQUAL;
1717
use const T_INLINE_ELSE;
18+
use const T_INLINE_THEN;
1819
use const T_OPEN_PARENTHESIS;
1920
use const T_OPEN_SHORT_ARRAY;
2021
use const T_OPEN_SQUARE_BRACKET;
@@ -30,6 +31,19 @@
3031
class TernaryOperatorHelper
3132
{
3233

34+
public static function isConditionOfTernaryOperator(File $phpcsFile, int $pointer): bool
35+
{
36+
$inlineThenPointer = TokenHelper::findNext($phpcsFile, T_INLINE_THEN, $pointer + 1);
37+
38+
if ($inlineThenPointer === null) {
39+
return false;
40+
}
41+
42+
$startPointer = self::getStartPointer($phpcsFile, $inlineThenPointer);
43+
44+
return $startPointer <= $pointer;
45+
}
46+
3347
public static function getElsePointer(File $phpcsFile, int $inlineThenPointer): int
3448
{
3549
$tokens = $phpcsFile->getTokens();

SlevomatCodingStandard/Sniffs/ControlStructures/RequireNullSafeObjectOperatorSniff.php

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,14 @@ public function register(): array
5858
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
5959
* @param File $phpcsFile
6060
* @param int $identicalPointer
61+
* @return int
6162
*/
62-
public function process(File $phpcsFile, $identicalPointer): void
63+
public function process(File $phpcsFile, $identicalPointer): int
6364
{
6465
$this->enable = SniffSettingsHelper::isEnabledByPhpVersion($this->enable, 80000);
6566

6667
if (!$this->enable) {
67-
return;
68+
return $identicalPointer + 1;
6869
}
6970

7071
$tokens = $phpcsFile->getTokens();
@@ -73,7 +74,7 @@ public function process(File $phpcsFile, $identicalPointer): void
7374
$pointerAfterIdentical = TokenHelper::findNextEffective($phpcsFile, $identicalPointer + 1);
7475

7576
if ($tokens[$pointerBeforeIdentical]['code'] !== T_NULL && $tokens[$pointerAfterIdentical]['code'] !== T_NULL) {
76-
return;
77+
return $identicalPointer + 1;
7778
}
7879

7980
$isYoda = $tokens[$pointerBeforeIdentical]['code'] === T_NULL;
@@ -83,7 +84,7 @@ public function process(File $phpcsFile, $identicalPointer): void
8384
$identificatorEndPointer = $this->findIdentificatorEnd($phpcsFile, $identificatorStartPointer);
8485

8586
if ($identificatorEndPointer === null) {
86-
return;
87+
return $pointerAfterIdentical + 1;
8788
}
8889

8990
$conditionStartPointer = $pointerBeforeIdentical;
@@ -93,7 +94,7 @@ public function process(File $phpcsFile, $identicalPointer): void
9394
$identificatorStartPointer = $this->findIdentificatorStart($phpcsFile, $identificatorEndPointer);
9495

9596
if ($identificatorStartPointer === null) {
96-
return;
97+
return $identificatorEndPointer + 1;
9798
}
9899

99100
$conditionStartPointer = $identificatorStartPointer;
@@ -108,10 +109,15 @@ public function process(File $phpcsFile, $identicalPointer): void
108109

109110
$allowedBooleanCondition = $tokens[$identicalPointer]['code'] === T_IS_NOT_IDENTICAL ? T_BOOLEAN_AND : T_BOOLEAN_OR;
110111
if ($tokens[$pointerAfterCondition]['code'] === $allowedBooleanCondition) {
111-
$this->checkNextCondition($phpcsFile, $identicalPointer, $conditionStartPointer, $identificator, $pointerAfterCondition);
112-
} elseif ($tokens[$pointerAfterCondition]['code'] === T_INLINE_THEN) {
112+
return $this->checkNextCondition($phpcsFile, $identicalPointer, $conditionStartPointer, $identificator, $pointerAfterCondition);
113+
}
114+
115+
if ($tokens[$pointerAfterCondition]['code'] === T_INLINE_THEN) {
113116
$this->checkTernaryOperator($phpcsFile, $identicalPointer, $conditionStartPointer, $identificator, $pointerAfterCondition);
117+
return $pointerAfterCondition + 1;
114118
}
119+
120+
return $identicalPointer + 1;
115121
}
116122

117123
private function checkTernaryOperator(
@@ -258,20 +264,20 @@ private function checkNextCondition(
258264
int $conditionStartPointer,
259265
string $identificator,
260266
int $nextConditionBooleanPointer
261-
): void
267+
): int
262268
{
263269
$nextIdentificatorPointers = $this->getNextIdentificator($phpcsFile, $nextConditionBooleanPointer);
264270

265271
if ($nextIdentificatorPointers === null) {
266-
return;
272+
return $nextConditionBooleanPointer;
267273
}
268274

269275
[$nextIdentificatorStartPointer, $nextIdentificatorEndPointer] = $nextIdentificatorPointers;
270276

271277
$nextIdentificator = IdentificatorHelper::getContent($phpcsFile, $nextIdentificatorStartPointer, $nextIdentificatorEndPointer);
272278

273279
if (!$this->areIdentificatorsCompatible($identificator, $nextIdentificator)) {
274-
return;
280+
return $nextConditionBooleanPointer;
275281
}
276282

277283
$pointerAfterNexIdentificator = TokenHelper::findNextEffective($phpcsFile, $nextIdentificatorEndPointer + 1);
@@ -282,13 +288,17 @@ private function checkNextCondition(
282288
$tokens[$pointerAfterNexIdentificator]['code'] !== $tokens[$identicalPointer]['code']
283289
&& !in_array($tokens[$pointerAfterNexIdentificator]['code'], [T_INLINE_THEN, T_SEMICOLON], true)
284290
) {
285-
return;
291+
return $nextConditionBooleanPointer;
292+
}
293+
294+
if (!in_array($tokens[$pointerAfterNexIdentificator]['code'], [T_IS_IDENTICAL, T_IS_NOT_IDENTICAL], true)) {
295+
return $nextConditionBooleanPointer;
286296
}
287297

288298
if ($tokens[$pointerAfterNexIdentificator]['code'] === T_IS_NOT_IDENTICAL) {
289299
$pointerAfterNotIdentical = TokenHelper::findNextEffective($phpcsFile, $pointerAfterNexIdentificator + 1);
290300
if ($tokens[$pointerAfterNotIdentical]['code'] !== T_NULL) {
291-
return;
301+
return $nextConditionBooleanPointer;
292302
}
293303
}
294304

@@ -302,9 +312,11 @@ private function checkNextCondition(
302312
$fix = $phpcsFile->addFixableError('Operator ?-> is required.', $identicalPointer, self::CODE_REQUIRED_NULL_SAFE_OBJECT_OPERATOR);
303313

304314
if (!$fix) {
305-
return;
315+
return $nextConditionBooleanPointer;
306316
}
307317

318+
$isConditionOfTernaryOperator = TernaryOperatorHelper::isConditionOfTernaryOperator($phpcsFile, $identicalPointer);
319+
308320
$phpcsFile->fixer->beginChangeset();
309321

310322
$phpcsFile->fixer->replaceToken($conditionStartPointer, sprintf('%s?%s', $identificator, $identificatorDifference));
@@ -314,6 +326,12 @@ private function checkNextCondition(
314326
}
315327

316328
$phpcsFile->fixer->endChangeset();
329+
330+
if ($isConditionOfTernaryOperator) {
331+
return TokenHelper::findNext($phpcsFile, T_INLINE_THEN, $identicalPointer + 1);
332+
}
333+
334+
return $nextConditionBooleanPointer;
317335
}
318336

319337
/**

tests/Sniffs/ControlStructures/RequireNullSafeObjectOperatorSniffTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public function testErrors(): void
2121
'enable' => true,
2222
]);
2323

24-
self::assertSame(15, $report->getErrorCount());
24+
self::assertSame(16, $report->getErrorCount());
2525

26-
foreach ([3, 4, 6, 7, 9, 10, 12, 13, 15, 18, 27] as $line) {
26+
foreach ([3, 4, 6, 7, 9, 10, 12, 13, 15, 18, 27, 34] as $line) {
2727
self::assertSniffError($report, $line, RequireNullSafeObjectOperatorSniff::CODE_REQUIRED_NULL_SAFE_OBJECT_OPERATOR);
2828
}
2929

tests/Sniffs/ControlStructures/data/requireNullSafeObjectOperatorErrors.fixed.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ function ($gatewayData, $response) {
2727
$this->getSerializedResponse($response),
2828
);
2929
};
30+
31+
// Must be last
32+
if ($b->getC()?->getD() !== null) {
33+
34+
}

tests/Sniffs/ControlStructures/data/requireNullSafeObjectOperatorErrors.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,8 @@ function ($gatewayData, $response) {
2929
$this->getSerializedResponse($response),
3030
);
3131
};
32+
33+
// Must be last
34+
if ($b->getC() !== null && $b->getC()->getD() !== null) {
35+
36+
}

tests/Sniffs/ControlStructures/data/requireNullSafeObjectOperatorNoErrors.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@
4444
$a = $b && $c->getD() !== null ? $c->getD()->getValue() : null;
4545

4646
$a = $b !== false && $c->getD() !== null ? $c->getD()->getValue() : null;
47+
48+
function ($something): bool {
49+
return $something->getA() !== null && $something->getA()->isBoolean();
50+
};

0 commit comments

Comments
 (0)