Skip to content

Commit

Permalink
Merge pull request #76 from PHPCSStandards/universal/new-anonclasspar…
Browse files Browse the repository at this point in the history
…entheses-sniffs

New `Universal.Classes.Disallow/RequireAnonClassParentheses` sniffs
  • Loading branch information
jrfnl authored Aug 2, 2020
2 parents 02f4ee9 + 36ad68c commit 942ac14
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 0 deletions.
20 changes: 20 additions & 0 deletions Universal/Docs/Classes/DisallowAnonClassParenthesesStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<documentation title="Disallow Anonymous Class Parentheses">
<standard>
<![CDATA[
Disallows the use of parentheses when declaring an anonymous class without passing parameters.
]]>
</standard>
<code_comparison>
<code title="Valid: Anonymous class without parentheses or with parameters.">
<![CDATA[
$anon = new class<em></em> {};
$anon = new class<em>($param)</em> {};
]]>
</code>
<code title="Invalid: Anonymous class with parentheses.">
<![CDATA[
$anon = new class<em>()</em> {};
]]>
</code>
</code_comparison>
</documentation>
19 changes: 19 additions & 0 deletions Universal/Docs/Classes/RequireAnonClassParenthesesStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<documentation title="Require Anonymous Class Parentheses">
<standard>
<![CDATA[
Require the use of parentheses when declaring an anonymous class.
]]>
</standard>
<code_comparison>
<code title="Valid: Anonymous class with parentheses.">
<![CDATA[
$anon = new class<em>()</em> {};
]]>
</code>
<code title="Invalid: Anonymous class without parentheses.">
<![CDATA[
$anon = new class<em></em> {};
]]>
</code>
</code_comparison>
</documentation>
98 changes: 98 additions & 0 deletions Universal/Sniffs/Classes/DisallowAnonClassParenthesesSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?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\Classes;

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

/**
* Forbid for an anonymous class declaration/instantiation to have parentheses, except when
* parameters are being passed.
*
* @since 1.0.0
*/
class DisallowAnonClassParenthesesSniff implements Sniff
{

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 1.0.0
*
* @return array
*/
public function register()
{
return [
\T_ANON_CLASS,
];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.0.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
|| $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS
) {
// No parentheses found.
$phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'no');
return;
}

if (isset($tokens[$nextNonEmpty]['parenthesis_closer']) === false) {
// Incomplete set of parentheses. Ignore.
$phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes');
return;
}

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

$phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes');

$fix = $phpcsFile->addFixableError(
'Parenthesis not allowed when creating a new anonymous class without passing parameters',
$stackPtr,
'Found'
);

if ($fix === true) {
$phpcsFile->fixer->beginChangeset();
for ($i = $nextNonEmpty; $i <= $closer; $i++) {
if (isset(Tokens::$commentTokens[$tokens[$i]['code']]) === true) {
continue;
}

$phpcsFile->fixer->replaceToken($i, '');
}
$phpcsFile->fixer->endChangeset();
}
}
}
75 changes: 75 additions & 0 deletions Universal/Sniffs/Classes/RequireAnonClassParenthesesSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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\Classes;

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

/**
* Require that an anonymous class declaration/instantiation has parentheses, i.e. `new class().
*
* @since 1.0.0
*/
class RequireAnonClassParenthesesSniff implements Sniff
{

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 1.0.0
*
* @return array
*/
public function register()
{
return [
\T_ANON_CLASS,
];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 1.0.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
&& $tokens[$nextNonEmpty]['code'] === \T_OPEN_PARENTHESIS
) {
// Parentheses found.
$phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'yes');
return;
}

$phpcsFile->recordMetric($stackPtr, 'Anon class declaration with parenthesis', 'no');

$fix = $phpcsFile->addFixableError(
'Parenthesis required when creating a new anonymous class.',
$stackPtr,
'Missing'
);

if ($fix === true) {
$phpcsFile->fixer->addContent($stackPtr, '()');
}
}
}
31 changes: 31 additions & 0 deletions Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* OK.
*/
$a = new MyClass();

$anon = new class { // OK.
public static function get_instance() {
return new self(); // Even though within anon class, it's not the anon class instantiation we're looking for.
}
}

$util->doSomething( new class {} ); // OK.
$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK, has parameter.
$anon = new class // Comment
($param) {}; // OK, has parameter.

/*
* Bad.
*/
$util->doSomething( new class() {} );
$b = new class ( ) extends SomeClass implements SomeInterface {};
$anon = new class // Comment
() {};

$b = new class ( /*comment*/ ) {};

// Live coding.
// Intentional parse error. This has to be the last test in the file.
$anon = new class()
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* OK.
*/
$a = new MyClass();

$anon = new class { // OK.
public static function get_instance() {
return new self(); // Even though within anon class, it's not the anon class instantiation we're looking for.
}
}

$util->doSomething( new class {} ); // OK.
$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK, has parameter.
$anon = new class // Comment
($param) {}; // OK, has parameter.

/*
* Bad.
*/
$util->doSomething( new class {} );
$b = new class extends SomeClass implements SomeInterface {};
$anon = new class // Comment
{};

$b = new class /*comment*/ {};

// Live coding.
// Intentional parse error. This has to be the last test in the file.
$anon = new class()
49 changes: 49 additions & 0 deletions Universal/Tests/Classes/DisallowAnonClassParenthesesUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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\Classes;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the DisallowAnonClassParentheses sniff.
*
* @covers PHPCSExtra\Universal\Sniffs\Classes\DisallowAnonClassParenthesesSniff
*
* @since 1.0.0
*/
class DisallowAnonClassParenthesesUnitTest extends AbstractSniffUnitTest
{

/**
* Returns the lines where errors should occur.
*
* @return array <int line number> => <int number of errors>
*/
public function getErrorList()
{
return [
22 => 1,
23 => 1,
24 => 1,
27 => 1,
];
}

/**
* Returns the lines where warnings should occur.
*
* @return array <int line number> => <int number of warnings>
*/
public function getWarningList()
{
return [];
}
}
27 changes: 27 additions & 0 deletions Universal/Tests/Classes/RequireAnonClassParenthesesUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* OK.
*/
$a = new MyClass();

$anon = new class() { // OK.
public static function get_instance() {
return new self(); // Even though within anon class, it's not the anon class instantiation we're looking for.
}
}

$util->doSomething( new class() {} ); // OK.
$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK.
$anon = new class // Comment
() {}; // OK.

/*
* Bad.
*/
$util->doSomething( new class {} );
$b = new class extends SomeClass implements SomeInterface {};

// Live coding.
// Intentional parse error. This has to be the last test in the file.
$anon = new class
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* OK.
*/
$a = new MyClass();

$anon = new class() { // OK.
public static function get_instance() {
return new self(); // Even though within anon class, it's not the anon class instantiation we're looking for.
}
}

$util->doSomething( new class() {} ); // OK.
$b = new class( 10 ) extends SomeClass implements SomeInterface {}; // OK.
$anon = new class // Comment
() {}; // OK.

/*
* Bad.
*/
$util->doSomething( new class() {} );
$b = new class() extends SomeClass implements SomeInterface {};

// Live coding.
// Intentional parse error. This has to be the last test in the file.
$anon = new class
Loading

0 comments on commit 942ac14

Please sign in to comment.