Skip to content

Commit

Permalink
✨ New Universal.WhiteSpace.AnonClassKeywordSpacing sniff
Browse files Browse the repository at this point in the history
Checks the amount of spacing between the `class` keyword and the open parenthesis (if any) for anonymous class declarations.

The expected amount of spacing is configurable and defaults to `0`.

The default value of `0` has been decided upon based on scanning a not insignificant number of codebases and determining the prevailing style used for anonymous classes.
It is also in line with the previously proposed [errata for PSR12](php-fig/fig-standards#1206).

Includes fixer.
Includes unit tests.
Includes documentation.
Includes metrics.
  • Loading branch information
jrfnl authored and dingo-d committed May 28, 2023
1 parent 772e7d8 commit 1e78d8e
Show file tree
Hide file tree
Showing 5 changed files with 373 additions and 0 deletions.
28 changes: 28 additions & 0 deletions Universal/Docs/WhiteSpace/AnonClassKeywordSpacingStandard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<documentation title="Anonymous Class Keyword Spacing">
<standard>
<![CDATA[
Checks the spacing between the "class" keyword and the open parenthesis for anonymous classes with parentheses.
The desired amount of spacing is configurable and defaults to no space.
]]>
</standard>
<code_comparison>
<code title="Valid: No space between the class keyword and the open parenthesis.">
<![CDATA[
$foo = new <em>class(</em>$param)
{
public function __construct($p) {}
};
]]>
</code>
<code title="Invalid: Space between the class keyword and the open parenthesis.">
<![CDATA[
$foo = new <em>class (</em>$param)
{
public function __construct($p) {}
};
]]>
</code>
</code_comparison>
</documentation>
79 changes: 79 additions & 0 deletions Universal/Sniffs/WhiteSpace/AnonClassKeywordSpacingSniff.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?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\WhiteSpace;

use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Fixers\SpacesFixer;

/**
* Checks the spacing between the "class" keyword and the open parenthesis for anonymous classes with parentheses.
*
* @since 1.0.0
*/
final class AnonClassKeywordSpacingSniff implements Sniff
{

/**
* The amount of spacing to demand between the class keyword and the open parenthesis.
*
* @since 1.0.0
*
* @var int
*/
public $spacing = 0;

/**
* 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, nothing to do.
return;
}

SpacesFixer::checkAndFix(
$phpcsFile,
$stackPtr,
$nextNonEmpty,
(int) $this->spacing,
'There must be %1$s between the class keyword and the open parenthesis for an anonymous class. Found: %2$s',
'Incorrect',
'error',
0,
'Anon class: space between keyword and open parenthesis'
);
}
}
103 changes: 103 additions & 0 deletions Universal/Tests/WhiteSpace/AnonClassKeywordSpacingUnitTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

/*
* OK.
*/
$anon = new class; // Parse error, not our concern.

// No parentheses, not our concern.
$anon = new class {};
$anon = new class
{
function foo();
};

$anonWithAttribute = new #[SomeAttribute('summary')] class {
public const SOME_STUFF = 'foo';
};

/*
* Default setting of 0 spaces.
*/

// Correct spacing between keyword and parentheses.
$anon = new class() {};
$anon = new class($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class () {};
$anon = new class ($foo) {};
$anon = new class
($foo)
{
function foo();
};

// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
$anon = new class/*comment*/() {};
$anon = new class /*comment*/ ($foo) {};
$anon = new class // comment.
($foo)
{
function foo();
};

/*
* Custom setting of 1 space.
*/

// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 1

// Correct spacing between keyword and parentheses.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too little space.
$anon = new class() {};
$anon = new class($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class ($foo) {};
$anon = new class
($foo)
{
function foo();
};

// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
$anon = new class/*comment*/() {};
$anon = new class /*comment*/ ($foo) {};
$anon = new class // comment.
($foo)
{
function foo();
};

/*
* Custom setting of 4 spaces.
*/

// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 4

// Correct spacing between keyword and parentheses.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too little space.
$anon = new class() {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class () {};
$anon = new class ($foo) {};
$anon = new class
($foo)
{
function foo();
};

// Reset property.
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 0

// Live coding - this has to be the last test in the file without a new line after it.
$anon = new class
100 changes: 100 additions & 0 deletions Universal/Tests/WhiteSpace/AnonClassKeywordSpacingUnitTest.inc.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

/*
* OK.
*/
$anon = new class; // Parse error, not our concern.

// No parentheses, not our concern.
$anon = new class {};
$anon = new class
{
function foo();
};

$anonWithAttribute = new #[SomeAttribute('summary')] class {
public const SOME_STUFF = 'foo';
};

/*
* Default setting of 0 spaces.
*/

// Correct spacing between keyword and parentheses.
$anon = new class() {};
$anon = new class($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class() {};
$anon = new class($foo) {};
$anon = new class($foo)
{
function foo();
};

// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
$anon = new class/*comment*/() {};
$anon = new class /*comment*/ ($foo) {};
$anon = new class // comment.
($foo)
{
function foo();
};

/*
* Custom setting of 1 space.
*/

// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 1

// Correct spacing between keyword and parentheses.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too little space.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class ($foo) {};
$anon = new class ($foo)
{
function foo();
};

// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
$anon = new class/*comment*/() {};
$anon = new class /*comment*/ ($foo) {};
$anon = new class // comment.
($foo)
{
function foo();
};

/*
* Custom setting of 4 spaces.
*/

// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 4

// Correct spacing between keyword and parentheses.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too little space.
$anon = new class () {};
$anon = new class ($paramA, $paramB) {};

// Incorrect spacing - too much space.
$anon = new class () {};
$anon = new class ($foo) {};
$anon = new class ($foo)
{
function foo();
};

// Reset property.
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 0

// Live coding - this has to be the last test in the file without a new line after it.
$anon = new class
63 changes: 63 additions & 0 deletions Universal/Tests/WhiteSpace/AnonClassKeywordSpacingUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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\WhiteSpace;

use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;

/**
* Unit test class for the AnonClassKeywordSpacing sniff.
*
* @covers PHPCSExtra\Universal\Sniffs\WhiteSpace\AnonClassKeywordSpacingSniff
*
* @since 1.0.0
*/
final class AnonClassKeywordSpacingUnitTest extends AbstractSniffUnitTest
{

/**
* Returns the lines where errors should occur.
*
* @return array<int, int>
*/
public function getErrorList()
{
return [
28 => 1,
29 => 1,
30 => 1,
37 => 1,
38 => 1,
39 => 1,
56 => 1,
57 => 1,
60 => 1,
61 => 1,
68 => 1,
69 => 1,
70 => 1,
87 => 1,
88 => 1,
91 => 1,
92 => 1,
93 => 1,
];
}

/**
* Returns the lines where warnings should occur.
*
* @return array<int, int>
*/
public function getWarningList()
{
return [];
}
}

0 comments on commit 1e78d8e

Please sign in to comment.