Skip to content

Commit

Permalink
PHP 8.3 | Generic/LowerCaseType: add support for typed constants
Browse files Browse the repository at this point in the history
This sniff is specifically targeted at type declarations.

PHP 8.3 introduces typed constants. This means that the sniff now also needs to check OO constant declarations.

Fixed now. Includes tests.
  • Loading branch information
jrfnl committed Feb 15, 2024
1 parent 7240235 commit b265092
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 47 deletions.
68 changes: 64 additions & 4 deletions src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function process(File $phpcsFile, $stackPtr)
}

/*
* Check property types.
* Check OO constant and property types.
*/

if (isset(Tokens::$ooScopeTokens[$tokens[$stackPtr]['code']]) === true) {
Expand All @@ -97,22 +97,82 @@ public function process(File $phpcsFile, $stackPtr)

for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
// Skip over potentially large docblocks.
if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG
if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG
&& isset($tokens[$i]['comment_closer']) === true
) {
$i = $tokens[$i]['comment_closer'];
continue;
}

// Skip over function declarations and everything nested within.
if ($tokens[$i]['code'] === \T_FUNCTION
if ($tokens[$i]['code'] === T_FUNCTION
&& isset($tokens[$i]['scope_closer']) === true
) {
$i = $tokens[$i]['scope_closer'];
continue;
}

if ($tokens[$i]['code'] !== \T_VARIABLE) {
if ($tokens[$i]['code'] === T_CONST) {
$ignore = Tokens::$emptyTokens;
$ignore[T_NULLABLE] = T_NULLABLE;

$startOfType = $phpcsFile->findNext($ignore, ($i + 1), null, true);
if ($startOfType === false) {
// Parse error/live coding. Nothing to do. Rest of loop is moot.
return;
}

$assignmentOperator = $phpcsFile->findNext([T_EQUAL, T_SEMICOLON], ($startOfType + 1));
if ($assignmentOperator === false || $tokens[$assignmentOperator]['code'] !== T_EQUAL) {
// Parse error/live coding. Nothing to do. Rest of loop is moot.
return;
}

$constName = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentOperator - 1), null, true);
if ($startOfType !== $constName) {
$endOfType = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($constName - 1), null, true);

$type = '';
$isUnionType = false;
$isIntersectionType = false;
for ($j = $startOfType; $j <= $endOfType; $j++) {
if (isset($ignore[$tokens[$j]['code']]) === true) {
continue;
}

if ($tokens[$j]['code'] === T_TYPE_UNION) {
$isUnionType = true;
}

if ($tokens[$j]['code'] === T_TYPE_INTERSECTION) {
$isIntersectionType = true;
}

$type .= $tokens[$j]['content'];
}

$error = 'PHP constant type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'ConstantTypeFound';

if ($isIntersectionType === true) {
// Intersection types don't support simple types.
} else if ($isUnionType === true) {
$this->processUnionType(
$phpcsFile,
$startOfType,
$endOfType,
$error,
$errorCode
);
} else if (isset($this->phpTypes[strtolower($type)]) === true) {
$this->processType($phpcsFile, $startOfType, $type, $error, $errorCode);
}
}//end if

continue;
}//end if

if ($tokens[$i]['code'] !== T_VARIABLE) {
continue;
}

Expand Down
30 changes: 30 additions & 0 deletions src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,36 @@ $arrow = fn (Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Float => $a * $

$cl = function (False $a, TRUE $b, Null $c): ?True {};

class TypedClassConstants
{
const UNTYPED = null;
const FLOAT = 'Reserved keyword as name is valid and should not be changed';
const OBJECT = 'Reserved keyword as name is valid and should not be changed';

const ClassName FIRST = null;
public const Int SECOND = 0;
private const ?BOOL THIRD = false;
public const Self FOURTH = null;
}
interface TypedInterfaceConstants
{
protected const PaRenT FIRST = null;
private const ARRAY SECOND = [];
public const Float THIRD = 2.5;
final const ?STRING FOURTH = 'fourth';
}
trait TypedTraitConstants {
const IterablE FIRST = null;
const Object SECOND = null;
const Mixed THIRD = 'third';
}
enum TypedEnumConstants {
public const Iterable|FALSE|NULL FIRST = null;
protected const SELF|Parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null;
private const ClassName|/*comment*/Float|STRING|False THIRD = 'third';
public const sTRing | aRRaY | FaLSe FOURTH = 'fourth';
}

// Intentional error, should be ignored by the sniff.
interface PropertiesNotAllowed {
public $notAllowed;
Expand Down
30 changes: 30 additions & 0 deletions src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,36 @@ $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : float => $a * $

$cl = function (false $a, true $b, null $c): ?true {};

class TypedClassConstants
{
const UNTYPED = null;
const FLOAT = 'Reserved keyword as name is valid and should not be changed';
const OBJECT = 'Reserved keyword as name is valid and should not be changed';

const ClassName FIRST = null;
public const int SECOND = 0;
private const ?bool THIRD = false;
public const self FOURTH = null;
}
interface TypedInterfaceConstants
{
protected const parent FIRST = null;
private const array SECOND = [];
public const float THIRD = 2.5;
final const ?string FOURTH = 'fourth';
}
trait TypedTraitConstants {
const iterable FIRST = null;
const object SECOND = null;
const mixed THIRD = 'third';
}
enum TypedEnumConstants {
public const iterable|false|null FIRST = null;
protected const self|parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null;
private const ClassName|/*comment*/float|string|false THIRD = 'third';
public const string | array | false FOURTH = 'fourth';
}

// Intentional error, should be ignored by the sniff.
interface PropertiesNotAllowed {
public $notAllowed;
Expand Down
100 changes: 57 additions & 43 deletions src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,48 +31,62 @@ final class LowerCaseTypeUnitTest extends AbstractSniffUnitTest
public function getErrorList()
{
return [
14 => 1,
15 => 1,
16 => 1,
17 => 1,
18 => 1,
21 => 4,
22 => 3,
23 => 3,
25 => 1,
26 => 2,
27 => 2,
32 => 4,
36 => 1,
37 => 1,
38 => 1,
39 => 1,
43 => 2,
44 => 1,
46 => 1,
49 => 1,
51 => 2,
53 => 1,
55 => 2,
60 => 1,
61 => 1,
62 => 1,
63 => 1,
64 => 1,
65 => 1,
66 => 1,
67 => 1,
68 => 1,
69 => 1,
71 => 3,
72 => 2,
73 => 3,
74 => 3,
78 => 3,
82 => 2,
85 => 1,
94 => 5,
96 => 4,
14 => 1,
15 => 1,
16 => 1,
17 => 1,
18 => 1,
21 => 4,
22 => 3,
23 => 3,
25 => 1,
26 => 2,
27 => 2,
32 => 4,
36 => 1,
37 => 1,
38 => 1,
39 => 1,
43 => 2,
44 => 1,
46 => 1,
49 => 1,
51 => 2,
53 => 1,
55 => 2,
60 => 1,
61 => 1,
62 => 1,
63 => 1,
64 => 1,
65 => 1,
66 => 1,
67 => 1,
68 => 1,
69 => 1,
71 => 3,
72 => 2,
73 => 3,
74 => 3,
78 => 3,
82 => 2,
85 => 1,
94 => 5,
96 => 4,
105 => 1,
106 => 1,
107 => 1,
111 => 1,
112 => 1,
113 => 1,
114 => 1,
117 => 1,
118 => 1,
119 => 1,
122 => 3,
123 => 2,
124 => 3,
125 => 3,
];

}//end getErrorList()
Expand All @@ -89,7 +103,7 @@ public function getErrorList()
public function getWarningList()
{
// Warning from getMemberProperties() about parse error.
return [100 => 1];
return [130 => 1];

}//end getWarningList()

Expand Down

0 comments on commit b265092

Please sign in to comment.