Skip to content

Commit 72657e1

Browse files
committed
✨ New Universal.WhiteSpace.AnonClassKeywordSpacing sniff
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.
1 parent e5f0af2 commit 72657e1

5 files changed

+377
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0"?>
2+
<documentation title="Anonymous Class Keyword Spacing">
3+
<standard>
4+
<![CDATA[
5+
Checks the spacing between the "class" keyword and the open parenthesis for anonymous classes with parentheses.
6+
7+
The desired amount of spacing is configurable and defaults to no space.
8+
]]>
9+
</standard>
10+
<code_comparison>
11+
<code title="Valid: No space between the class keyword and the open parenthesis.">
12+
<![CDATA[
13+
$foo = new <em>class(</em>$param)
14+
{
15+
public function __construct($p) {}
16+
};
17+
]]>
18+
</code>
19+
<code title="Invalid: Space between the class keyword and the open parenthesis.">
20+
<![CDATA[
21+
$foo = new <em>class (</em>$param)
22+
{
23+
public function __construct($p) {}
24+
};
25+
]]>
26+
</code>
27+
</code_comparison>
28+
</documentation>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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\WhiteSpace;
12+
13+
use PHP_CodeSniffer\Sniffs\Sniff;
14+
use PHP_CodeSniffer\Files\File;
15+
use PHP_CodeSniffer\Util\Tokens;
16+
use PHPCSUtils\Fixers\SpacesFixer;
17+
18+
/**
19+
* Checks the spacing between the "class" keyword and the open parenthesis for anonymous classes with parentheses.
20+
*
21+
* @since 1.0.0
22+
*/
23+
final class AnonClassKeywordSpacingSniff implements Sniff
24+
{
25+
26+
/**
27+
* The amount of spacing to demand between the class keyword and the open parenthesis.
28+
*
29+
* @var int
30+
*/
31+
public $spacing = 0;
32+
33+
/**
34+
* Returns an array of tokens this test wants to listen for.
35+
*
36+
* @since 1.0.0
37+
*
38+
* @return array
39+
*/
40+
public function register()
41+
{
42+
return [\T_ANON_CLASS];
43+
}
44+
45+
/**
46+
* Processes this test, when one of its tokens is encountered.
47+
*
48+
* @since 1.0.0
49+
*
50+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
51+
* @param int $stackPtr The position of the current token
52+
* in the stack passed in $tokens.
53+
*
54+
* @return void
55+
*/
56+
public function process(File $phpcsFile, $stackPtr)
57+
{
58+
// Check if there are parentheses.
59+
$tokens = $phpcsFile->getTokens();
60+
61+
$nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
62+
if ($nextNonEmpty === false || $tokens[$nextNonEmpty]['code'] !== \T_OPEN_PARENTHESIS) {
63+
return;
64+
}
65+
66+
// Check spacing
67+
// Auto-fix when not a comment bteween.
68+
SpacesFixer::checkAndFix(
69+
$phpcsFile,
70+
$stackPtr,
71+
$nextNonEmpty,
72+
(int) $this->spacing,
73+
'There must be %1$s between the class keyword and the open parenthesis for an anonymous class. Found: %2$s',
74+
'Incorrect',
75+
'error',
76+
0,
77+
'Anon class: space between keyword and open parenthesis'
78+
);
79+
80+
// $phpcsFile->recordMetric($reportPtr, self::METRIC_NAME_SRC, 'same namespace');
81+
// $phpcsFile->addError($error, $reportPtr, $errorCode, $data);
82+
}
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* OK.
5+
*/
6+
$anon = new class; // Parse error, not our concern.
7+
8+
// No parentheses, not our concern.
9+
$anon = new class {};
10+
$anon = new class
11+
{
12+
function foo();
13+
};
14+
15+
$anonWithAttribute = new #[SomeAttribute('summary')] class {
16+
public const SOME_STUFF = 'foo';
17+
};
18+
19+
/*
20+
* Default setting of 0 spaces.
21+
*/
22+
23+
// Correct spacing between keyword and parentheses.
24+
$anon = new class() {};
25+
$anon = new class($paramA, $paramB) {};
26+
27+
// Incorrect spacing - too much space.
28+
$anon = new class () {};
29+
$anon = new class ($foo) {};
30+
$anon = new class
31+
($foo)
32+
{
33+
function foo();
34+
};
35+
36+
// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
37+
$anon = new class/*comment*/() {};
38+
$anon = new class /*comment*/ ($foo) {};
39+
$anon = new class // comment.
40+
($foo)
41+
{
42+
function foo();
43+
};
44+
45+
/*
46+
* Custom setting of 1 space.
47+
*/
48+
49+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 1
50+
51+
// Correct spacing between keyword and parentheses.
52+
$anon = new class () {};
53+
$anon = new class ($paramA, $paramB) {};
54+
55+
// Incorrect spacing - too little space.
56+
$anon = new class() {};
57+
$anon = new class($paramA, $paramB) {};
58+
59+
// Incorrect spacing - too much space.
60+
$anon = new class ($foo) {};
61+
$anon = new class
62+
($foo)
63+
{
64+
function foo();
65+
};
66+
67+
// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
68+
$anon = new class/*comment*/() {};
69+
$anon = new class /*comment*/ ($foo) {};
70+
$anon = new class // comment.
71+
($foo)
72+
{
73+
function foo();
74+
};
75+
76+
/*
77+
* Custom setting of 4 spaces.
78+
*/
79+
80+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 4
81+
82+
// Correct spacing between keyword and parentheses.
83+
$anon = new class () {};
84+
$anon = new class ($paramA, $paramB) {};
85+
86+
// Incorrect spacing - too little space.
87+
$anon = new class() {};
88+
$anon = new class ($paramA, $paramB) {};
89+
90+
// Incorrect spacing - too much space.
91+
$anon = new class () {};
92+
$anon = new class ($foo) {};
93+
$anon = new class
94+
($foo)
95+
{
96+
function foo();
97+
};
98+
99+
// Reset property.
100+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 0
101+
102+
// Live coding - this has to be the last test in the file without a new line after it.
103+
$anon = new class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
/*
4+
* OK.
5+
*/
6+
$anon = new class; // Parse error, not our concern.
7+
8+
// No parentheses, not our concern.
9+
$anon = new class {};
10+
$anon = new class
11+
{
12+
function foo();
13+
};
14+
15+
$anonWithAttribute = new #[SomeAttribute('summary')] class {
16+
public const SOME_STUFF = 'foo';
17+
};
18+
19+
/*
20+
* Default setting of 0 spaces.
21+
*/
22+
23+
// Correct spacing between keyword and parentheses.
24+
$anon = new class() {};
25+
$anon = new class($paramA, $paramB) {};
26+
27+
// Incorrect spacing - too much space.
28+
$anon = new class() {};
29+
$anon = new class($foo) {};
30+
$anon = new class($foo)
31+
{
32+
function foo();
33+
};
34+
35+
// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
36+
$anon = new class/*comment*/() {};
37+
$anon = new class /*comment*/ ($foo) {};
38+
$anon = new class // comment.
39+
($foo)
40+
{
41+
function foo();
42+
};
43+
44+
/*
45+
* Custom setting of 1 space.
46+
*/
47+
48+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 1
49+
50+
// Correct spacing between keyword and parentheses.
51+
$anon = new class () {};
52+
$anon = new class ($paramA, $paramB) {};
53+
54+
// Incorrect spacing - too little space.
55+
$anon = new class () {};
56+
$anon = new class ($paramA, $paramB) {};
57+
58+
// Incorrect spacing - too much space.
59+
$anon = new class ($foo) {};
60+
$anon = new class ($foo)
61+
{
62+
function foo();
63+
};
64+
65+
// Incorrect spacing - comment between keyword and parentheses, not auto-fixable.
66+
$anon = new class/*comment*/() {};
67+
$anon = new class /*comment*/ ($foo) {};
68+
$anon = new class // comment.
69+
($foo)
70+
{
71+
function foo();
72+
};
73+
74+
/*
75+
* Custom setting of 4 spaces.
76+
*/
77+
78+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 4
79+
80+
// Correct spacing between keyword and parentheses.
81+
$anon = new class () {};
82+
$anon = new class ($paramA, $paramB) {};
83+
84+
// Incorrect spacing - too little space.
85+
$anon = new class () {};
86+
$anon = new class ($paramA, $paramB) {};
87+
88+
// Incorrect spacing - too much space.
89+
$anon = new class () {};
90+
$anon = new class ($foo) {};
91+
$anon = new class ($foo)
92+
{
93+
function foo();
94+
};
95+
96+
// Reset property.
97+
// phpcs:set Universal.WhiteSpace.AnonClassKeywordSpacing spacing 0
98+
99+
// Live coding - this has to be the last test in the file without a new line after it.
100+
$anon = new class
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\WhiteSpace;
12+
13+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffUnitTest;
14+
15+
/**
16+
* Unit test class for the AnonClassKeywordSpacing sniff.
17+
*
18+
* @covers PHPCSExtra\Universal\Sniffs\WhiteSpace\AnonClassKeywordSpacingSniff
19+
*
20+
* @since 1.0.0
21+
*/
22+
final class AnonClassKeywordSpacingUnitTest extends AbstractSniffUnitTest
23+
{
24+
25+
/**
26+
* Returns the lines where errors should occur.
27+
*
28+
* @return array<int, int>
29+
*/
30+
public function getErrorList()
31+
{
32+
return [
33+
28 => 1,
34+
29 => 1,
35+
30 => 1,
36+
37 => 1,
37+
38 => 1,
38+
39 => 1,
39+
56 => 1,
40+
57 => 1,
41+
60 => 1,
42+
61 => 1,
43+
68 => 1,
44+
69 => 1,
45+
70 => 1,
46+
87 => 1,
47+
88 => 1,
48+
91 => 1,
49+
92 => 1,
50+
93 => 1,
51+
];
52+
}
53+
54+
/**
55+
* Returns the lines where warnings should occur.
56+
*
57+
* @return array<int, int>
58+
*/
59+
public function getWarningList()
60+
{
61+
return [];
62+
}
63+
}

0 commit comments

Comments
 (0)