Skip to content

Commit b265092

Browse files
committed
PHP 8.3 | Generic/LowerCaseType: add support for typed constants
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.
1 parent 7240235 commit b265092

File tree

4 files changed

+181
-47
lines changed

4 files changed

+181
-47
lines changed

src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public function process(File $phpcsFile, $stackPtr)
8787
}
8888

8989
/*
90-
* Check property types.
90+
* Check OO constant and property types.
9191
*/
9292

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

9898
for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
9999
// Skip over potentially large docblocks.
100-
if ($tokens[$i]['code'] === \T_DOC_COMMENT_OPEN_TAG
100+
if ($tokens[$i]['code'] === T_DOC_COMMENT_OPEN_TAG
101101
&& isset($tokens[$i]['comment_closer']) === true
102102
) {
103103
$i = $tokens[$i]['comment_closer'];
104104
continue;
105105
}
106106

107107
// Skip over function declarations and everything nested within.
108-
if ($tokens[$i]['code'] === \T_FUNCTION
108+
if ($tokens[$i]['code'] === T_FUNCTION
109109
&& isset($tokens[$i]['scope_closer']) === true
110110
) {
111111
$i = $tokens[$i]['scope_closer'];
112112
continue;
113113
}
114114

115-
if ($tokens[$i]['code'] !== \T_VARIABLE) {
115+
if ($tokens[$i]['code'] === T_CONST) {
116+
$ignore = Tokens::$emptyTokens;
117+
$ignore[T_NULLABLE] = T_NULLABLE;
118+
119+
$startOfType = $phpcsFile->findNext($ignore, ($i + 1), null, true);
120+
if ($startOfType === false) {
121+
// Parse error/live coding. Nothing to do. Rest of loop is moot.
122+
return;
123+
}
124+
125+
$assignmentOperator = $phpcsFile->findNext([T_EQUAL, T_SEMICOLON], ($startOfType + 1));
126+
if ($assignmentOperator === false || $tokens[$assignmentOperator]['code'] !== T_EQUAL) {
127+
// Parse error/live coding. Nothing to do. Rest of loop is moot.
128+
return;
129+
}
130+
131+
$constName = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentOperator - 1), null, true);
132+
if ($startOfType !== $constName) {
133+
$endOfType = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($constName - 1), null, true);
134+
135+
$type = '';
136+
$isUnionType = false;
137+
$isIntersectionType = false;
138+
for ($j = $startOfType; $j <= $endOfType; $j++) {
139+
if (isset($ignore[$tokens[$j]['code']]) === true) {
140+
continue;
141+
}
142+
143+
if ($tokens[$j]['code'] === T_TYPE_UNION) {
144+
$isUnionType = true;
145+
}
146+
147+
if ($tokens[$j]['code'] === T_TYPE_INTERSECTION) {
148+
$isIntersectionType = true;
149+
}
150+
151+
$type .= $tokens[$j]['content'];
152+
}
153+
154+
$error = 'PHP constant type declarations must be lowercase; expected "%s" but found "%s"';
155+
$errorCode = 'ConstantTypeFound';
156+
157+
if ($isIntersectionType === true) {
158+
// Intersection types don't support simple types.
159+
} else if ($isUnionType === true) {
160+
$this->processUnionType(
161+
$phpcsFile,
162+
$startOfType,
163+
$endOfType,
164+
$error,
165+
$errorCode
166+
);
167+
} else if (isset($this->phpTypes[strtolower($type)]) === true) {
168+
$this->processType($phpcsFile, $startOfType, $type, $error, $errorCode);
169+
}
170+
}//end if
171+
172+
continue;
173+
}//end if
174+
175+
if ($tokens[$i]['code'] !== T_VARIABLE) {
116176
continue;
117177
}
118178

src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,36 @@ $arrow = fn (Int $a, String $b, BOOL $c, Array $d, Foo\Bar $e) : Float => $a * $
9595

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

98+
class TypedClassConstants
99+
{
100+
const UNTYPED = null;
101+
const FLOAT = 'Reserved keyword as name is valid and should not be changed';
102+
const OBJECT = 'Reserved keyword as name is valid and should not be changed';
103+
104+
const ClassName FIRST = null;
105+
public const Int SECOND = 0;
106+
private const ?BOOL THIRD = false;
107+
public const Self FOURTH = null;
108+
}
109+
interface TypedInterfaceConstants
110+
{
111+
protected const PaRenT FIRST = null;
112+
private const ARRAY SECOND = [];
113+
public const Float THIRD = 2.5;
114+
final const ?STRING FOURTH = 'fourth';
115+
}
116+
trait TypedTraitConstants {
117+
const IterablE FIRST = null;
118+
const Object SECOND = null;
119+
const Mixed THIRD = 'third';
120+
}
121+
enum TypedEnumConstants {
122+
public const Iterable|FALSE|NULL FIRST = null;
123+
protected const SELF|Parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null;
124+
private const ClassName|/*comment*/Float|STRING|False THIRD = 'third';
125+
public const sTRing | aRRaY | FaLSe FOURTH = 'fourth';
126+
}
127+
98128
// Intentional error, should be ignored by the sniff.
99129
interface PropertiesNotAllowed {
100130
public $notAllowed;

src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.inc.fixed

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,36 @@ $arrow = fn (int $a, string $b, bool $c, array $d, Foo\Bar $e) : float => $a * $
9595

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

98+
class TypedClassConstants
99+
{
100+
const UNTYPED = null;
101+
const FLOAT = 'Reserved keyword as name is valid and should not be changed';
102+
const OBJECT = 'Reserved keyword as name is valid and should not be changed';
103+
104+
const ClassName FIRST = null;
105+
public const int SECOND = 0;
106+
private const ?bool THIRD = false;
107+
public const self FOURTH = null;
108+
}
109+
interface TypedInterfaceConstants
110+
{
111+
protected const parent FIRST = null;
112+
private const array SECOND = [];
113+
public const float THIRD = 2.5;
114+
final const ?string FOURTH = 'fourth';
115+
}
116+
trait TypedTraitConstants {
117+
const iterable FIRST = null;
118+
const object SECOND = null;
119+
const mixed THIRD = 'third';
120+
}
121+
enum TypedEnumConstants {
122+
public const iterable|false|null FIRST = null;
123+
protected const self|parent /* comment */ |\Fully\Qualified\ClassName|UnQualifiedClass SECOND = null;
124+
private const ClassName|/*comment*/float|string|false THIRD = 'third';
125+
public const string | array | false FOURTH = 'fourth';
126+
}
127+
98128
// Intentional error, should be ignored by the sniff.
99129
interface PropertiesNotAllowed {
100130
public $notAllowed;

src/Standards/Generic/Tests/PHP/LowerCaseTypeUnitTest.php

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -31,48 +31,62 @@ final class LowerCaseTypeUnitTest extends AbstractSniffUnitTest
3131
public function getErrorList()
3232
{
3333
return [
34-
14 => 1,
35-
15 => 1,
36-
16 => 1,
37-
17 => 1,
38-
18 => 1,
39-
21 => 4,
40-
22 => 3,
41-
23 => 3,
42-
25 => 1,
43-
26 => 2,
44-
27 => 2,
45-
32 => 4,
46-
36 => 1,
47-
37 => 1,
48-
38 => 1,
49-
39 => 1,
50-
43 => 2,
51-
44 => 1,
52-
46 => 1,
53-
49 => 1,
54-
51 => 2,
55-
53 => 1,
56-
55 => 2,
57-
60 => 1,
58-
61 => 1,
59-
62 => 1,
60-
63 => 1,
61-
64 => 1,
62-
65 => 1,
63-
66 => 1,
64-
67 => 1,
65-
68 => 1,
66-
69 => 1,
67-
71 => 3,
68-
72 => 2,
69-
73 => 3,
70-
74 => 3,
71-
78 => 3,
72-
82 => 2,
73-
85 => 1,
74-
94 => 5,
75-
96 => 4,
34+
14 => 1,
35+
15 => 1,
36+
16 => 1,
37+
17 => 1,
38+
18 => 1,
39+
21 => 4,
40+
22 => 3,
41+
23 => 3,
42+
25 => 1,
43+
26 => 2,
44+
27 => 2,
45+
32 => 4,
46+
36 => 1,
47+
37 => 1,
48+
38 => 1,
49+
39 => 1,
50+
43 => 2,
51+
44 => 1,
52+
46 => 1,
53+
49 => 1,
54+
51 => 2,
55+
53 => 1,
56+
55 => 2,
57+
60 => 1,
58+
61 => 1,
59+
62 => 1,
60+
63 => 1,
61+
64 => 1,
62+
65 => 1,
63+
66 => 1,
64+
67 => 1,
65+
68 => 1,
66+
69 => 1,
67+
71 => 3,
68+
72 => 2,
69+
73 => 3,
70+
74 => 3,
71+
78 => 3,
72+
82 => 2,
73+
85 => 1,
74+
94 => 5,
75+
96 => 4,
76+
105 => 1,
77+
106 => 1,
78+
107 => 1,
79+
111 => 1,
80+
112 => 1,
81+
113 => 1,
82+
114 => 1,
83+
117 => 1,
84+
118 => 1,
85+
119 => 1,
86+
122 => 3,
87+
123 => 2,
88+
124 => 3,
89+
125 => 3,
7690
];
7791

7892
}//end getErrorList()
@@ -89,7 +103,7 @@ public function getErrorList()
89103
public function getWarningList()
90104
{
91105
// Warning from getMemberProperties() about parse error.
92-
return [100 => 1];
106+
return [130 => 1];
93107

94108
}//end getWarningList()
95109

0 commit comments

Comments
 (0)