Skip to content

Commit 17c2e7d

Browse files
committed
Universal/EnumCaseName: new sniff to verify PascalCase enum case names
Add the `Universal.NamingConventions.EnumCaseName` sniff which verifies that enum case names are declared using PascalCase, as required by PER Coding Style 2.0, section 9. The sniff registers `T_ENUM_CASE` and checks the following case name with `Common::isCamelCaps()` in strict class-format mode, so a name must start with an uppercase letter and must not contain consecutive uppercase letters. Fixes 444
1 parent cfdda7d commit 17c2e7d

6 files changed

Lines changed: 211 additions & 0 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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="Enum Case Name"
5+
>
6+
<standard>
7+
<![CDATA[
8+
Enum case declarations must use PascalCase capitalization.
9+
]]>
10+
</standard>
11+
<code_comparison>
12+
<code title="Valid: Enum case name in PascalCase.">
13+
<![CDATA[
14+
enum Suit {
15+
case <em>Hearts</em>;
16+
case <em>JsonValue</em>;
17+
}
18+
]]>
19+
</code>
20+
<code title="Invalid: Enum case name not in PascalCase.">
21+
<![CDATA[
22+
enum Suit {
23+
case <em>hearts</em>;
24+
case <em>JSONValue</em>;
25+
}
26+
]]>
27+
</code>
28+
</code_comparison>
29+
</documentation>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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\NamingConventions;
12+
13+
use PHP_CodeSniffer\Files\File;
14+
use PHP_CodeSniffer\Sniffs\Sniff;
15+
use PHP_CodeSniffer\Util\Common;
16+
use PHP_CodeSniffer\Util\Tokens;
17+
18+
/**
19+
* Verifies that enum case names are declared using PascalCase.
20+
*
21+
* PascalCase is interpreted strictly: a name must start with an uppercase letter and must not
22+
* contain two consecutive uppercase letters, so "JsonValue" is accepted while "JSONValue" is
23+
* not.
24+
*
25+
* @since 1.6.0
26+
*/
27+
final class EnumCaseNameSniff implements Sniff
28+
{
29+
30+
/**
31+
* Registers the tokens that this sniff wants to listen for.
32+
*
33+
* @since 1.6.0
34+
*
35+
* @return array<int|string>
36+
*/
37+
public function register()
38+
{
39+
return [\T_ENUM_CASE];
40+
}
41+
42+
/**
43+
* Processes this sniff, when one of its tokens is encountered.
44+
*
45+
* @since 1.6.0
46+
*
47+
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
48+
* @param int $stackPtr The position of the current token in the token stack.
49+
*
50+
* @return void
51+
*/
52+
public function process(File $phpcsFile, $stackPtr)
53+
{
54+
$tokens = $phpcsFile->getTokens();
55+
56+
$caseNamePtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
57+
if ($caseNamePtr === false || $tokens[$caseNamePtr]['code'] !== \T_STRING) {
58+
// Live coding/parse error.
59+
return;
60+
}
61+
62+
$caseName = $tokens[$caseNamePtr]['content'];
63+
64+
if (Common::isCamelCaps($caseName, true) === false) {
65+
$phpcsFile->addError(
66+
'Enum case name "%s" is not in PascalCase',
67+
$caseNamePtr,
68+
'Invalid',
69+
[$caseName]
70+
);
71+
}
72+
}
73+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* Valid enum case names (PascalCase).
5+
*/
6+
7+
enum ValidCaseNames {
8+
case Hearts;
9+
case DiamondsRed;
10+
case Base64Url;
11+
case /* comment */ Spades;
12+
case
13+
// Name on the next line.
14+
Clubs;
15+
}
16+
17+
enum ValidBackedEnum: string {
18+
case A = 'A';
19+
}
20+
21+
/*
22+
* Invalid enum case names.
23+
*/
24+
25+
enum InvalidCaseNames {
26+
case diamondsRed;
27+
case diamonds_red;
28+
case HEARTS;
29+
case
30+
// Name on the next line.
31+
Base64URL;
32+
}
33+
34+
enum InvalidBackedEnum: int {
35+
case _Hidden = 2;
36+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
// Intentional parse error (incomplete enum case declaration).
4+
// This should be the only test in the file.
5+
6+
enum Incomplete {
7+
case
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
// Intentional parse error (enum case keyword followed by a non-name token).
4+
// This should be the only test in the file.
5+
6+
enum Incomplete {
7+
case ;
8+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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\NamingConventions;
12+
13+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase;
14+
15+
/**
16+
* Unit test class for the EnumCaseName sniff.
17+
*
18+
* @covers PHPCSExtra\Universal\Sniffs\NamingConventions\EnumCaseNameSniff
19+
*
20+
* @since 1.6.0
21+
*/
22+
final class EnumCaseNameUnitTest 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 'EnumCaseNameUnitTest.1.inc':
36+
return [
37+
26 => 1,
38+
27 => 1,
39+
28 => 1,
40+
31 => 1,
41+
35 => 1,
42+
];
43+
44+
default:
45+
return [];
46+
}
47+
}
48+
49+
/**
50+
* Returns the lines where warnings should occur.
51+
*
52+
* @return array<int, int> Key is the line number, value is the number of expected warnings.
53+
*/
54+
public function getWarningList()
55+
{
56+
return [];
57+
}
58+
}

0 commit comments

Comments
 (0)