Skip to content

Commit d2f07a9

Browse files
authored
Merge pull request #120 from magento/MC-18816
MC-18816: Impelement an better check for ThrowCatchSniff
2 parents 4a1c8d2 + 1585734 commit d2f07a9

File tree

4 files changed

+84
-34
lines changed

4 files changed

+84
-34
lines changed

Diff for: Magento2/Sniffs/Exceptions/ThrowCatchSniff.php

+59-33
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright © Magento. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
67
namespace Magento2\Sniffs\Exceptions;
78

89
use function array_slice;
@@ -33,7 +34,7 @@ class ThrowCatchSniff implements Sniff
3334
*/
3435
public function register()
3536
{
36-
return [T_FUNCTION, T_CLOSURE];
37+
return [T_TRY];
3738
}
3839

3940
/**
@@ -42,53 +43,78 @@ public function register()
4243
public function process(File $phpcsFile, $stackPtr)
4344
{
4445
$tokens = $phpcsFile->getTokens();
45-
if (!isset($tokens[$stackPtr]['scope_closer'])) {
46-
// Probably an interface method no check
47-
return;
48-
}
46+
$endOfStatement = $phpcsFile->findEndOfStatement($stackPtr);
4947

50-
$closeBrace = $tokens[$stackPtr]['scope_closer'];
51-
$throwTags = [];
52-
$catchTags = [];
48+
$throwClassNames = [];
49+
$searchForNextThrow = $stackPtr;
5350

54-
for ($i = $stackPtr; $i < $closeBrace; $i++) {
55-
$token = $tokens[$i];
56-
if ($token['code'] === T_CATCH) {
57-
$catchTags[] = $token;
58-
}
59-
if ($token['code'] === T_THROW) {
60-
$throwTags[] = $i;
51+
// search for all throws
52+
do {
53+
$throwTag = $phpcsFile->findNext(T_THROW, $searchForNextThrow, $endOfStatement);
54+
55+
if ($throwTag === false) {
56+
break;
6157
}
62-
}
6358

64-
if (count($catchTags) === 0 || count($throwTags) === 0) {
65-
// No catch or throw found no check
66-
return;
59+
$throwClassNames[] = $this->getFullClassName($tokens, $throwTag + 1);
60+
61+
$searchForNextThrow = $throwTag + 1;
62+
} while ($throwTag !== false);
63+
64+
if (empty($throwClassNames)) {
65+
return; // is not relevant not throw in try found.
6766
}
6867

6968
$catchClassNames = [];
70-
$throwClassNames = [];
71-
72-
// find all relevant classes in catch
73-
foreach ($catchTags as $catchTag) {
74-
$start = $catchTag['parenthesis_opener'];
75-
$end = $catchTag['parenthesis_closer'];
7669

77-
$match = $phpcsFile->findNext(T_STRING, $start, $end);
78-
$catchClassNames[$match] = $tokens[$match]['content'];
79-
}
70+
// TRY statements need to check until the end of all CATCH statements.
71+
do {
72+
$nextToken = $phpcsFile->findNext(T_WHITESPACE, ($endOfStatement + 1), null, true);
73+
if ($tokens[$nextToken]['code'] === T_CATCH) {
74+
$endOfStatement = $tokens[$nextToken]['scope_closer'];
75+
$catchClassNames[$nextToken] = $this->getFullClassName($tokens, $nextToken + 1);
76+
} else {
77+
break;
78+
}
79+
} while (isset($tokens[$nextToken]['scope_closer']) === true);
8080

81-
// find all relevant classes in throws
82-
foreach ($throwTags as $throwTag) {
83-
$match = $phpcsFile->findNext(T_STRING, $throwTag);
84-
$throwClassNames[] = $tokens[$match]['content'];
81+
if (empty($catchClassNames)) {
82+
return; // is not relevant no catch found
8583
}
8684

8785
$throwClassNames = array_flip($throwClassNames);
8886
foreach ($catchClassNames as $match => $catchClassName) {
89-
if (array_key_exists($catchClassName, $throwClassNames)) {
87+
if (isset($throwClassNames[$catchClassName])) {
9088
$phpcsFile->addWarning($this->warningMessage, $match, $this->warningCode);
9189
}
9290
}
9391
}
92+
93+
/**
94+
* Get the full class name with namespace.
95+
*
96+
* @param array $tokens
97+
* @param int $startPos
98+
* @return string
99+
*/
100+
private function getFullClassName(array $tokens, $startPos)
101+
{
102+
$fullName = "";
103+
$endOfClassName = [T_SEMICOLON => 0, T_CLOSE_PARENTHESIS => 0];
104+
105+
$tokenCount = count($tokens);
106+
for ($i = $startPos; $i <= $tokenCount; $i++) {
107+
$type = $tokens[$i]['code'];
108+
109+
if ($type === T_STRING || $type === T_NS_SEPARATOR) {
110+
$fullName .= $tokens[$i]['content'];
111+
}
112+
113+
if (array_key_exists($type, $endOfClassName)) {
114+
break; // line end each
115+
}
116+
}
117+
118+
return $fullName;
119+
}
94120
}

Diff for: Magento2/Tests/Exceptions/ThrowCatchUnitTest.1.inc

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
// This file contains all false positive cases that were found with previous implementation
4+
// Check Exceptions MUST NOT handled in same function should fin nothing there
5+
6+
/**
7+
* @throws \Magento\Framework\Exception\LocalizedException
8+
*/
9+
function creatDir()
10+
{
11+
try {
12+
// call internal code that throw an Exception
13+
throw new \Magento\Framework\Exception\LocalizedException('Nice user friendly message');
14+
}
15+
catch (\Magento\Framework\Exception\FileSystemException $e) {
16+
throw new \Magento\Framework\Exception\LocalizedException('Nice user friendly message');
17+
}
18+
}

Diff for: Magento2/Tests/Exceptions/ThrowCatchUnitTest.php

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright © Magento. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
67
namespace Magento2\Tests\Exceptions;
78

89
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
@@ -23,8 +24,13 @@ protected function getErrorList()
2324
/**
2425
* @inheritdoc
2526
*/
26-
protected function getWarningList()
27+
protected function getWarningList($testFile = '')
2728
{
29+
30+
if ($testFile === 'ThrowCatchUnitTest.1.inc') {
31+
return [];
32+
}
33+
2834
return [
2935
41 => 1,
3036
120 => 1,

0 commit comments

Comments
 (0)