Skip to content

Commit 66ae57f

Browse files
committed
Generic/LowerCaseType: add support for examining DNF types
The `Generic.PHP.LowerCaseType` sniff needs to be updated to also handle non-lowercase types which are part of a DNF type declaration. This commit updates the `processUnionType()` method to not only examine union types, but to examine all multi-token types and to do so in a slightly more performant manner and calls that method now for all multi-token type declarations. Note: The method name now doesn't properly cover the functionality anymore, however, renaming the method would be a breaking change as the class is not `final` and the method not `private`. Includes unit tests. Related to 105 Closes 105
1 parent 538b96a commit 66ae57f

File tree

4 files changed

+88
-58
lines changed

4 files changed

+88
-58
lines changed

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

+53-57
Original file line numberDiff line numberDiff line change
@@ -132,40 +132,23 @@ public function process(File $phpcsFile, $stackPtr)
132132
if ($startOfType !== $constName) {
133133
$endOfType = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($constName - 1), null, true);
134134

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-
154135
$error = 'PHP constant type declarations must be lowercase; expected "%s" but found "%s"';
155136
$errorCode = 'ConstantTypeFound';
156137

157-
if ($isIntersectionType === true) {
158-
// Intersection types don't support simple types.
159-
} else if ($isUnionType === true) {
138+
if ($startOfType !== $endOfType) {
139+
// Multi-token type.
160140
$this->processUnionType(
161141
$phpcsFile,
162142
$startOfType,
163143
$endOfType,
164144
$error,
165145
$errorCode
166146
);
167-
} else if (isset($this->phpTypes[strtolower($type)]) === true) {
168-
$this->processType($phpcsFile, $startOfType, $type, $error, $errorCode);
147+
} else {
148+
$type = $tokens[$startOfType]['content'];
149+
if (isset($this->phpTypes[strtolower($type)]) === true) {
150+
$this->processType($phpcsFile, $startOfType, $type, $error, $errorCode);
151+
}
169152
}
170153
}//end if
171154

@@ -195,9 +178,8 @@ public function process(File $phpcsFile, $stackPtr)
195178
$error = 'PHP property type declarations must be lowercase; expected "%s" but found "%s"';
196179
$errorCode = 'PropertyTypeFound';
197180

198-
if (strpos($type, '&') !== false) {
199-
// Intersection types don't support simple types.
200-
} else if (strpos($type, '|') !== false) {
181+
if ($props['type_token'] !== $props['type_end_token']) {
182+
// Multi-token type.
201183
$this->processUnionType(
202184
$phpcsFile,
203185
$props['type_token'],
@@ -227,9 +209,8 @@ public function process(File $phpcsFile, $stackPtr)
227209
$error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"';
228210
$errorCode = 'ReturnTypeFound';
229211

230-
if (strpos($returnType, '&') !== false) {
231-
// Intersection types don't support simple types.
232-
} else if (strpos($returnType, '|') !== false) {
212+
if ($props['return_type_token'] !== $props['return_type_end_token']) {
213+
// Multi-token type.
233214
$this->processUnionType(
234215
$phpcsFile,
235216
$props['return_type_token'],
@@ -259,9 +240,8 @@ public function process(File $phpcsFile, $stackPtr)
259240
$error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"';
260241
$errorCode = 'ParamTypeFound';
261242

262-
if (strpos($typeHint, '&') !== false) {
263-
// Intersection types don't support simple types.
264-
} else if (strpos($typeHint, '|') !== false) {
243+
if ($param['type_hint_token'] !== $param['type_hint_end_token']) {
244+
// Multi-token type.
265245
$this->processUnionType(
266246
$phpcsFile,
267247
$param['type_hint_token'],
@@ -279,7 +259,9 @@ public function process(File $phpcsFile, $stackPtr)
279259

280260

281261
/**
282-
* Processes a union type declaration.
262+
* Processes a multi-token type declaration.
263+
*
264+
* {@internal The method name is superseded by the reality, but changing it would be a BC-break.}
283265
*
284266
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
285267
* @param int $typeDeclStart The position of the start of the type token.
@@ -291,37 +273,51 @@ public function process(File $phpcsFile, $stackPtr)
291273
*/
292274
protected function processUnionType(File $phpcsFile, $typeDeclStart, $typeDeclEnd, $error, $errorCode)
293275
{
294-
$tokens = $phpcsFile->getTokens();
295-
$current = $typeDeclStart;
296-
297-
do {
298-
$endOfType = $phpcsFile->findNext(T_TYPE_UNION, $current, $typeDeclEnd);
299-
if ($endOfType === false) {
300-
// This must be the last type in the union.
301-
$endOfType = ($typeDeclEnd + 1);
302-
}
276+
$tokens = $phpcsFile->getTokens();
277+
$typeTokenCount = 0;
278+
$typeStart = null;
279+
$type = '';
303280

304-
$hasNsSep = $phpcsFile->findNext(T_NS_SEPARATOR, $current, $endOfType);
305-
if ($hasNsSep !== false) {
306-
// Multi-token class based type. Ignore.
307-
$current = ($endOfType + 1);
281+
for ($i = $typeDeclStart; $i <= $typeDeclEnd; $i++) {
282+
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) {
308283
continue;
309284
}
310285

311-
// Type consisting of a single token.
312-
$startOfType = $phpcsFile->findNext(Tokens::$emptyTokens, $current, $endOfType, true);
313-
if ($startOfType === false) {
314-
// Parse error.
315-
return;
286+
if ($tokens[$i]['code'] === T_TYPE_UNION
287+
|| $tokens[$i]['code'] === T_TYPE_INTERSECTION
288+
|| $tokens[$i]['code'] === T_TYPE_OPEN_PARENTHESIS
289+
|| $tokens[$i]['code'] === T_TYPE_CLOSE_PARENTHESIS
290+
) {
291+
if ($typeTokenCount === 1
292+
&& $type !== ''
293+
&& isset($this->phpTypes[strtolower($type)]) === true
294+
) {
295+
$this->processType($phpcsFile, $typeStart, $type, $error, $errorCode);
296+
}
297+
298+
// Reset for the next type in the type string.
299+
$typeTokenCount = 0;
300+
$typeStart = null;
301+
$type = '';
302+
303+
continue;
316304
}
317305

318-
$type = $tokens[$startOfType]['content'];
319-
if (isset($this->phpTypes[strtolower($type)]) === true) {
320-
$this->processType($phpcsFile, $startOfType, $type, $error, $errorCode);
306+
if (isset($typeStart) === false) {
307+
$typeStart = $i;
321308
}
322309

323-
$current = ($endOfType + 1);
324-
} while ($current <= $typeDeclEnd);
310+
++$typeTokenCount;
311+
$type .= $tokens[$i]['content'];
312+
}//end for
313+
314+
// Handle type at end of type string.
315+
if ($typeTokenCount === 1
316+
&& $type !== ''
317+
&& isset($this->phpTypes[strtolower($type)]) === true
318+
) {
319+
$this->processType($phpcsFile, $typeStart, $type, $error, $errorCode);
320+
}
325321

326322
}//end processUnionType()
327323

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

+14
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ enum TypedEnumConstants {
125125
public const sTRing | aRRaY | FaLSe FOURTH = 'fourth';
126126
}
127127

128+
class DNFTypes {
129+
const (Parent&Something)|Float CONST_NAME = 1.5;
130+
131+
public readonly TRUE|(\A&B) $prop;
132+
133+
function DNFParamTypes (
134+
null|(\Package\ClassName&\Package\Other_Class)|INT $DNFinMiddle,
135+
(\Package\ClassName&\Package\Other_Class)|ARRAY $parensAtStart,
136+
False|(\Package\ClassName&\Package\Other_Class) $parentAtEnd,
137+
) {}
138+
139+
function DNFReturnTypes ($var): object|(Self&\Package\Other_Class)|sTRINg|false {}
140+
}
141+
128142
// Intentional error, should be ignored by the sniff.
129143
interface PropertiesNotAllowed {
130144
public $notAllowed;

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

+14
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,20 @@ enum TypedEnumConstants {
125125
public const string | array | false FOURTH = 'fourth';
126126
}
127127

128+
class DNFTypes {
129+
const (parent&Something)|float CONST_NAME = 1.5;
130+
131+
public readonly true|(\A&B) $prop;
132+
133+
function DNFParamTypes (
134+
null|(\Package\ClassName&\Package\Other_Class)|int $DNFinMiddle,
135+
(\Package\ClassName&\Package\Other_Class)|array $parensAtStart,
136+
false|(\Package\ClassName&\Package\Other_Class) $parentAtEnd,
137+
) {}
138+
139+
function DNFReturnTypes ($var): object|(self&\Package\Other_Class)|string|false {}
140+
}
141+
128142
// Intentional error, should be ignored by the sniff.
129143
interface PropertiesNotAllowed {
130144
public $notAllowed;

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ public function getErrorList()
8787
123 => 2,
8888
124 => 3,
8989
125 => 3,
90+
129 => 2,
91+
131 => 1,
92+
134 => 1,
93+
135 => 1,
94+
136 => 1,
95+
139 => 2,
9096
];
9197

9298
}//end getErrorList()
@@ -103,7 +109,7 @@ public function getErrorList()
103109
public function getWarningList()
104110
{
105111
// Warning from getMemberProperties() about parse error.
106-
return [130 => 1];
112+
return [144 => 1];
107113

108114
}//end getWarningList()
109115

0 commit comments

Comments
 (0)