Skip to content

Commit 96c7948

Browse files
authored
Merge pull request #399 from PHPCSStandards/feature/new-universal-disallowexitdieparentheses-sniff
✨ New `Universal.PHP.DisallowExitDieParentheses` sniff
2 parents 108e829 + 08a1e64 commit 96c7948

9 files changed

+326
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0"?>
2+
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd"
4+
title="Disallow Exit/Die Parentheses"
5+
>
6+
<standard>
7+
<![CDATA[
8+
Disallows the use of parentheses when calling `exit` or `die` without passing the `$status` parameter.
9+
]]>
10+
</standard>
11+
<code_comparison>
12+
<code title="Valid: Exit/die without parentheses or with parameter.">
13+
<![CDATA[
14+
die<em></em>;
15+
exit<em>($status)</em>;
16+
]]>
17+
</code>
18+
<code title="Invalid: Exit/die with parentheses, but without parameter.">
19+
<![CDATA[
20+
die<em>()</em>;
21+
exit<em>()</em>;
22+
]]>
23+
</code>
24+
</code_comparison>
25+
</documentation>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4+
*
5+
* @package PHPCSExtra
6+
* @copyright 2020 PHPCSExtra Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSExtra
9+
*/
10+
11+
namespace PHPCSExtra\Universal\Sniffs\PHP;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Sniffs\Sniff;
15+
use PHP_CodeSniffer\Util\Tokens;
16+
17+
/**
18+
* Forbids calling `exit`/`die` with parentheses, except when the `$status` parameter is passed.
19+
*
20+
* @since 1.5.0
21+
*/
22+
final class DisallowExitDieParenthesesSniff implements Sniff
23+
{
24+
25+
/**
26+
* Name of the metric.
27+
*
28+
* @since 1.5.0
29+
*
30+
* @var string
31+
*/
32+
const METRIC_NAME = 'Exit/die with parentheses';
33+
34+
/**
35+
* Returns an array of tokens this test wants to listen for.
36+
*
37+
* @since 1.5.0
38+
*
39+
* @return array<int|string>
40+
*/
41+
public function register()
42+
{
43+
return [\T_EXIT];
44+
}
45+
46+
/**
47+
* Processes this test, when one of its tokens is encountered.
48+
*
49+
* @since 1.5.0
50+
*
51+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
52+
* @param int $stackPtr The position of the current token
53+
* in the stack passed in $tokens.
54+
*
55+
* @return void
56+
*/
57+
public function process(File $phpcsFile, $stackPtr)
58+
{
59+
$tokens = $phpcsFile->getTokens();
60+
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
61+
if ($nextNonEmpty === false) {
62+
// Live coding. Do not flag (yet).
63+
return;
64+
}
65+
66+
if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
67+
// No parentheses found.
68+
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no');
69+
return;
70+
}
71+
72+
if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
73+
// Incomplete set of parentheses. Ignore.
74+
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes');
75+
return;
76+
}
77+
78+
$opener = $nextNonEmpty;
79+
$closer = $tokens[$opener]['parenthesis_closer'];
80+
$hasParams = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true);
81+
if ($hasParams !== false) {
82+
// There is something between the parentheses. Ignore.
83+
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes, with parameter(s)');
84+
return;
85+
}
86+
87+
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes');
88+
89+
$fix = $phpcsFile->addFixableError(
90+
'Parentheses not allowed when calling %s without passing parameters',
91+
$stackPtr,
92+
'Found',
93+
[\strtolower(\ltrim($tokens[$stackPtr]['content'], '\\'))]
94+
);
95+
96+
if ($fix === true) {
97+
$phpcsFile->fixer->beginChangeset();
98+
99+
for ($i = ($stackPtr + 1); $i <= $closer; $i++) {
100+
if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) {
101+
continue;
102+
}
103+
104+
$phpcsFile->fixer->replaceToken($i, '');
105+
}
106+
107+
$phpcsFile->fixer->endChangeset();
108+
}
109+
}
110+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* Not our targets.
5+
* These tests are mostly safeguards against tokenizer issues in PHPCS, at this moment (3.13.4/4.0.0), these wouldn't be tokenized as T_EXIT anyway.
6+
*/
7+
class Mirror {
8+
const exit = 0;
9+
const die = 1;
10+
11+
public $exit = 0;
12+
protected $die = 1;
13+
14+
function exit() {}
15+
function die() {}
16+
17+
function useMe() {
18+
echo self::exit;
19+
echo static::die;
20+
echo parent::exit;
21+
echo AnotherObject::die;
22+
echo $this->exit;
23+
echo $this->die;
24+
echo $this->exit();
25+
echo $this->die();
26+
}
27+
}
28+
29+
// Invalid PHP, but not our concern. Not our target.
30+
namespace\Sub\exit;
31+
Partially\Qualified\die;
32+
\Fully\Qualified\exit;
33+
34+
/*
35+
* OK.
36+
*/
37+
exit;
38+
die;
39+
EXIT;
40+
Die;
41+
\exit;
42+
\die;
43+
exit ($status);
44+
\die /*comment*/ (10);
45+
46+
/*
47+
* Bad.
48+
*/
49+
exit();
50+
die();
51+
Exit(/*comment*/);
52+
DIE ();
53+
\exit ( );
54+
\die();
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* Not our targets.
5+
* These tests are mostly safeguards against tokenizer issues in PHPCS, at this moment (3.13.4/4.0.0), these wouldn't be tokenized as T_EXIT anyway.
6+
*/
7+
class Mirror {
8+
const exit = 0;
9+
const die = 1;
10+
11+
public $exit = 0;
12+
protected $die = 1;
13+
14+
function exit() {}
15+
function die() {}
16+
17+
function useMe() {
18+
echo self::exit;
19+
echo static::die;
20+
echo parent::exit;
21+
echo AnotherObject::die;
22+
echo $this->exit;
23+
echo $this->die;
24+
echo $this->exit();
25+
echo $this->die();
26+
}
27+
}
28+
29+
// Invalid PHP, but not our concern. Not our target.
30+
namespace\Sub\exit;
31+
Partially\Qualified\die;
32+
\Fully\Qualified\exit;
33+
34+
/*
35+
* OK.
36+
*/
37+
exit;
38+
die;
39+
EXIT;
40+
Die;
41+
\exit;
42+
\die;
43+
exit ($status);
44+
\die /*comment*/ (10);
45+
46+
/*
47+
* Bad.
48+
*/
49+
exit;
50+
die;
51+
Exit/*comment*/;
52+
DIE;
53+
\exit;
54+
\die;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
// Intentional parse error/live coding.
4+
// This must be the only test in the file.
5+
// This code sniff should **not** be flagged (yet).
6+
exit
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
// Intentional parse error/live coding.
4+
// This must be the only test in the file.
5+
// This code sniff should **not** be flagged (yet).
6+
die/*comment*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
// Intentional parse error/live coding.
4+
// This must be the only test in the file.
5+
// This code sniff should **not** be flagged (yet).
6+
exit(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
// Intentional parse error/live coding.
4+
// This must be the only test in the file.
5+
// This code sniff should **not** be flagged (yet).
6+
die($status
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
/**
3+
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4+
*
5+
* @package PHPCSExtra
6+
* @copyright 2020 PHPCSExtra Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSExtra
9+
*/
10+
11+
namespace PHPCSExtra\Universal\Tests\PHP;
12+
13+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase;
14+
15+
/**
16+
* Unit test class for the DisallowExitDieParentheses sniff.
17+
*
18+
* @covers PHPCSExtra\Universal\Sniffs\PHP\DisallowExitDieParenthesesSniff
19+
*
20+
* @since 1.5.0
21+
*/
22+
final class DisallowExitDieParenthesesUnitTest extends AbstractSniffTestCase
23+
{
24+
25+
/**
26+
* Returns the lines where errors should occur.
27+
*
28+
* @param string $testFile The name of the file being tested.
29+
*
30+
* @return array<int, int> Key is the line number, value is the number of expected errors.
31+
*/
32+
public function getErrorList($testFile = '')
33+
{
34+
switch ($testFile) {
35+
case 'DisallowExitDieParenthesesUnitTest.1.inc':
36+
return [
37+
49 => 1,
38+
50 => 1,
39+
51 => 1,
40+
52 => 1,
41+
53 => 1,
42+
54 => 1,
43+
];
44+
45+
default:
46+
return [];
47+
}
48+
}
49+
50+
/**
51+
* Returns the lines where warnings should occur.
52+
*
53+
* @return array<int, int> Key is the line number, value is the number of expected warnings.
54+
*/
55+
public function getWarningList()
56+
{
57+
return [];
58+
}
59+
}

0 commit comments

Comments
 (0)