Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP 8.3 | Generic/LowerCaseType: add support for typed constants #331

Merged
merged 1 commit into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading