Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Universal/Docs/PHP/DisallowExitDieParenthesesStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<documentation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://phpcsstandards.github.io/PHPCSDevTools/phpcsdocs.xsd"
title="Disallow Exit/Die Parentheses"
>
<standard>
<![CDATA[
Disallows the use of parentheses when calling `exit` or `die` without passing the `$status` parameter.
]]>
</standard>
<code_comparison>
<code title="Valid: Exit/die without parentheses or with parameter.">
<![CDATA[
die<em></em>;
exit<em>($status)</em>;
]]>
</code>
<code title="Invalid: Exit/die with parentheses, but without parameter.">
<![CDATA[
die<em>()</em>;
exit<em>()</em>;
]]>
</code>
</code_comparison>
</documentation>
110 changes: 110 additions & 0 deletions Universal/Sniffs/PHP/DisallowExitDieParenthesesSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/

namespace PHPCSExtra\Universal\Sniffs\PHP;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;

/**
* Forbids calling `exit`/`die` with parentheses, except when the `$status` parameter is passed.
*
* @since 1.5.0
*/
final class DisallowExitDieParenthesesSniff implements Sniff
{

/**
* Name of the metric.
*
* @since 1.5.0
*
* @var string
*/
const METRIC_NAME = 'Exit/die with parentheses';

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 1.5.0
*
* @return array<int|string>
*/
public function register()
{
return [\T_EXIT];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.5.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($nextNonEmpty === false) {
// Live coding. Do not flag (yet).
return;
}

if ($tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
// No parentheses found.
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'no');
return;
}

if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
// Incomplete set of parentheses. Ignore.
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes');
return;
}

$opener = $nextNonEmpty;
$closer = $tokens[$opener]['parenthesis_closer'];
$hasParams = $phpcsFile->findNext(Tokens::$emptyTokens, ($opener + 1), $closer, true);
if ($hasParams !== false) {
// There is something between the parentheses. Ignore.
$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes, with parameter(s)');
return;
}

$phpcsFile->recordMetric($stackPtr, self::METRIC_NAME, 'yes');

$fix = $phpcsFile->addFixableError(
'Parentheses not allowed when calling %s without passing parameters',
$stackPtr,
'Found',
[\strtolower(\ltrim($tokens[$stackPtr]['content'], '\\'))]
);

if ($fix === true) {
$phpcsFile->fixer->beginChangeset();

for ($i = ($stackPtr + 1); $i <= $closer; $i++) {
if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) {
continue;
}

$phpcsFile->fixer->replaceToken($i, '');
}

$phpcsFile->fixer->endChangeset();
}
}
}
54 changes: 54 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.1.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* Not our targets.
* 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.
*/
class Mirror {
const exit = 0;
const die = 1;

public $exit = 0;
protected $die = 1;

function exit() {}
function die() {}

function useMe() {
echo self::exit;
echo static::die;
echo parent::exit;
echo AnotherObject::die;
echo $this->exit;
echo $this->die;
echo $this->exit();
echo $this->die();
}
}

// Invalid PHP, but not our concern. Not our target.
namespace\Sub\exit;
Partially\Qualified\die;
\Fully\Qualified\exit;

/*
* OK.
*/
exit;
die;
EXIT;
Die;
\exit;
\die;
exit ($status);
\die /*comment*/ (10);

/*
* Bad.
*/
exit();
die();
Exit(/*comment*/);
DIE ();
\exit ( );
\die();
54 changes: 54 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.1.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* Not our targets.
* 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.
*/
class Mirror {
const exit = 0;
const die = 1;

public $exit = 0;
protected $die = 1;

function exit() {}
function die() {}

function useMe() {
echo self::exit;
echo static::die;
echo parent::exit;
echo AnotherObject::die;
echo $this->exit;
echo $this->die;
echo $this->exit();
echo $this->die();
}
}

// Invalid PHP, but not our concern. Not our target.
namespace\Sub\exit;
Partially\Qualified\die;
\Fully\Qualified\exit;

/*
* OK.
*/
exit;
die;
EXIT;
Die;
\exit;
\die;
exit ($status);
\die /*comment*/ (10);

/*
* Bad.
*/
exit;
die;
Exit/*comment*/;
DIE;
\exit;
\die;
6 changes: 6 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.2.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

// Intentional parse error/live coding.
// This must be the only test in the file.
// This code sniff should **not** be flagged (yet).
exit
6 changes: 6 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.3.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

// Intentional parse error/live coding.
// This must be the only test in the file.
// This code sniff should **not** be flagged (yet).
die/*comment*/
6 changes: 6 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.4.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

// Intentional parse error/live coding.
// This must be the only test in the file.
// This code sniff should **not** be flagged (yet).
exit(
6 changes: 6 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.5.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

// Intentional parse error/live coding.
// This must be the only test in the file.
// This code sniff should **not** be flagged (yet).
die($status
59 changes: 59 additions & 0 deletions Universal/Tests/PHP/DisallowExitDieParenthesesUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
/**
* PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
*
* @package PHPCSExtra
* @copyright 2020 PHPCSExtra Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCSStandards/PHPCSExtra
*/

namespace PHPCSExtra\Universal\Tests\PHP;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase;

/**
* Unit test class for the DisallowExitDieParentheses sniff.
*
* @covers PHPCSExtra\Universal\Sniffs\PHP\DisallowExitDieParenthesesSniff
*
* @since 1.5.0
*/
final class DisallowExitDieParenthesesUnitTest extends AbstractSniffTestCase
{

/**
* Returns the lines where errors should occur.
*
* @param string $testFile The name of the file being tested.
*
* @return array<int, int> Key is the line number, value is the number of expected errors.
*/
public function getErrorList($testFile = '')
{
switch ($testFile) {
case 'DisallowExitDieParenthesesUnitTest.1.inc':
return [
49 => 1,
50 => 1,
51 => 1,
52 => 1,
53 => 1,
54 => 1,
];

default:
return [];
}
}

/**
* Returns the lines where warnings should occur.
*
* @return array<int, int> Key is the line number, value is the number of expected warnings.
*/
public function getWarningList()
{
return [];
}
}