Skip to content

Commit 7b7af06

Browse files
BCFile::getMemberProperties(): sync with upstream (#646)
* BCFile::getMemberProperties(): sync with upstream PHPCS 3.12.0 adds support for reporting if properties are final; polyfill the upstream method and copy over the tests. Ref: PHPCSStandards/PHP_CodeSniffer#834 Closes #645 * Update `Variables::getMemberProperties()` * Linting * BCFile::getMemberProperties(): make compatible with PHPCS 4.x ... and remove some redundant end comments.
1 parent 060222e commit 7b7af06

File tree

5 files changed

+376
-2
lines changed

5 files changed

+376
-2
lines changed

PHPCSUtils/BackCompat/BCFile.php

+141-2
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
539539
* 'scope_specified' => boolean, // TRUE if the scope was explicitly specified.
540540
* 'is_static' => boolean, // TRUE if the static keyword was found.
541541
* 'is_readonly' => boolean, // TRUE if the readonly keyword was found.
542+
* 'is_final' => boolean, // TRUE if the final keyword was found.
542543
* 'type' => string, // The type of the var (empty if no type specified).
543544
* 'type_token' => integer|false, // The stack pointer to the start of the type
544545
* // or FALSE if there is no type.
@@ -553,7 +554,7 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
553554
*
554555
* Changelog for the PHPCS native function:
555556
* - Introduced in PHPCS 0.0.5.
556-
* - The upstream method has received no significant updates since PHPCS 3.10.1.
557+
* - PHPCS 3.12.0: report final properties
557558
*
558559
* @see \PHP_CodeSniffer\Files\File::getMemberProperties() Original source.
559560
* @see \PHPCSUtils\Utils\Variables::getMemberProperties() PHPCSUtils native improved version.
@@ -572,7 +573,145 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
572573
*/
573574
public static function getMemberProperties(File $phpcsFile, $stackPtr)
574575
{
575-
return $phpcsFile->getMemberProperties($stackPtr);
576+
$tokens = $phpcsFile->getTokens();
577+
if ($tokens[$stackPtr]['code'] !== T_VARIABLE) {
578+
throw new RuntimeException('$stackPtr must be of type T_VARIABLE');
579+
}
580+
581+
$conditions = array_keys($tokens[$stackPtr]['conditions']);
582+
$ptr = array_pop($conditions);
583+
if (isset($tokens[$ptr]) === false
584+
|| ($tokens[$ptr]['code'] !== T_CLASS
585+
&& $tokens[$ptr]['code'] !== T_ANON_CLASS
586+
&& $tokens[$ptr]['code'] !== T_TRAIT)
587+
) {
588+
if (isset($tokens[$ptr]) === true
589+
&& ($tokens[$ptr]['code'] === T_INTERFACE
590+
|| $tokens[$ptr]['code'] === T_ENUM)
591+
) {
592+
// T_VARIABLEs in interfaces/enums can actually be method arguments
593+
// but they won't be seen as being inside the method because there
594+
// are no scope openers and closers for abstract methods. If it is in
595+
// parentheses, we can be pretty sure it is a method argument.
596+
if (isset($tokens[$stackPtr]['nested_parenthesis']) === false
597+
|| empty($tokens[$stackPtr]['nested_parenthesis']) === true
598+
) {
599+
$error = 'Possible parse error: %ss may not include member vars';
600+
$code = sprintf('Internal.ParseError.%sHasMemberVar', ucfirst($tokens[$ptr]['content']));
601+
$data = [strtolower($tokens[$ptr]['content'])];
602+
$phpcsFile->addWarning($error, $stackPtr, $code, $data);
603+
return [];
604+
}
605+
} else {
606+
throw new RuntimeException('$stackPtr is not a class member var');
607+
}
608+
}
609+
610+
// Make sure it's not a method parameter.
611+
if (empty($tokens[$stackPtr]['nested_parenthesis']) === false) {
612+
$parenthesis = array_keys($tokens[$stackPtr]['nested_parenthesis']);
613+
$deepestOpen = array_pop($parenthesis);
614+
if ($deepestOpen > $ptr
615+
&& isset($tokens[$deepestOpen]['parenthesis_owner']) === true
616+
&& $tokens[$tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
617+
) {
618+
throw new RuntimeException('$stackPtr is not a class member var');
619+
}
620+
}
621+
622+
$valid = Collections::propertyModifierKeywords();
623+
$valid += Tokens::$emptyTokens;
624+
625+
$scope = 'public';
626+
$scopeSpecified = false;
627+
$isStatic = false;
628+
$isReadonly = false;
629+
$isFinal = false;
630+
631+
$startOfStatement = $phpcsFile->findPrevious(
632+
[
633+
T_SEMICOLON,
634+
T_OPEN_CURLY_BRACKET,
635+
T_CLOSE_CURLY_BRACKET,
636+
T_ATTRIBUTE_END,
637+
],
638+
($stackPtr - 1)
639+
);
640+
641+
for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
642+
if (isset($valid[$tokens[$i]['code']]) === false) {
643+
break;
644+
}
645+
646+
switch ($tokens[$i]['code']) {
647+
case T_PUBLIC:
648+
$scope = 'public';
649+
$scopeSpecified = true;
650+
break;
651+
case T_PRIVATE:
652+
$scope = 'private';
653+
$scopeSpecified = true;
654+
break;
655+
case T_PROTECTED:
656+
$scope = 'protected';
657+
$scopeSpecified = true;
658+
break;
659+
case T_STATIC:
660+
$isStatic = true;
661+
break;
662+
case T_READONLY:
663+
$isReadonly = true;
664+
break;
665+
case T_FINAL:
666+
$isFinal = true;
667+
break;
668+
}
669+
}
670+
671+
$type = '';
672+
$typeToken = false;
673+
$typeEndToken = false;
674+
$nullableType = false;
675+
$propertyTypeTokens = Collections::propertyTypeTokens();
676+
677+
if ($i < $stackPtr) {
678+
// We've found a type.
679+
for ($i; $i < $stackPtr; $i++) {
680+
if ($tokens[$i]['code'] === T_VARIABLE) {
681+
// Hit another variable in a group definition.
682+
break;
683+
}
684+
685+
if ($tokens[$i]['code'] === T_NULLABLE) {
686+
$nullableType = true;
687+
}
688+
689+
if (isset($propertyTypeTokens[$tokens[$i]['code']]) === true) {
690+
$typeEndToken = $i;
691+
if ($typeToken === false) {
692+
$typeToken = $i;
693+
}
694+
695+
$type .= $tokens[$i]['content'];
696+
}
697+
}
698+
699+
if ($type !== '' && $nullableType === true) {
700+
$type = '?' . $type;
701+
}
702+
}
703+
704+
return [
705+
'scope' => $scope,
706+
'scope_specified' => $scopeSpecified,
707+
'is_static' => $isStatic,
708+
'is_readonly' => $isReadonly,
709+
'is_final' => $isFinal,
710+
'type' => $type,
711+
'type_token' => $typeToken,
712+
'type_end_token' => $typeEndToken,
713+
'nullable_type' => $nullableType,
714+
];
576715
}
577716

578717
/**

PHPCSUtils/Tokens/Collections.php

+1
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ final class Collections
473473
\T_STATIC => \T_STATIC,
474474
\T_VAR => \T_VAR,
475475
\T_READONLY => \T_READONLY,
476+
\T_FINAL => \T_FINAL,
476477
];
477478

478479
/**

PHPCSUtils/Utils/Variables.php

+6
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ final class Variables
104104
* 'scope_specified' => boolean, // TRUE if the scope was explicitly specified.
105105
* 'is_static' => boolean, // TRUE if the static keyword was found.
106106
* 'is_readonly' => boolean, // TRUE if the readonly keyword was found.
107+
* 'is_final' => boolean, // TRUE if the final keyword was found.
107108
* 'type' => string, // The type of the var (empty if no type specified).
108109
* 'type_token' => integer|false, // The stack pointer to the start of the type
109110
* // or FALSE if there is no type.
@@ -149,6 +150,7 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr)
149150
$scopeSpecified = false;
150151
$isStatic = false;
151152
$isReadonly = false;
153+
$isFinal = false;
152154

153155
$startOfStatement = $phpcsFile->findPrevious(
154156
[
@@ -184,6 +186,9 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr)
184186
case \T_READONLY:
185187
$isReadonly = true;
186188
break;
189+
case \T_FINAL:
190+
$isFinal = true;
191+
break;
187192
}
188193
}
189194

@@ -225,6 +230,7 @@ public static function getMemberProperties(File $phpcsFile, $stackPtr)
225230
'scope_specified' => $scopeSpecified,
226231
'is_static' => $isStatic,
227232
'is_readonly' => $isReadonly,
233+
'is_final' => $isFinal,
228234
'type' => $type,
229235
'type_token' => $typeToken,
230236
'type_end_token' => $typeEndToken,

Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc

+21
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,24 @@ trait DNFTypes {
354354
// Intentional fatal error - nullable operator cannot be combined with DNF.
355355
var ?(A&\Pck\B)|bool $propD;
356356
}
357+
358+
class WithFinalProperties {
359+
/* testPHP84FinalPublicTypedProp */
360+
final public string $val1;
361+
/* testPHP84FinalProtectedTypedProp */
362+
final protected string $val2;
363+
/* testPHP84FinalMiddleTypedProp */
364+
public final string $val3;
365+
/* testPHP84FinalMiddleStaticTypedProp */
366+
public final static string $val4;
367+
/* testPHP84FinalLastTypedProp */
368+
public readonly final string $val5;
369+
/* testPHP84FinalImplicitVisibilityTypedProp */
370+
final string $val6;
371+
/* testPHP84FinalImplicitVisibilityProp */
372+
final $val7;
373+
/* testPHP84FinalNullableTypedProp */
374+
final public ?string $val8;
375+
/* testPHP84FinalComplexTypedProp */
376+
final public (Foo&\Bar)|bool $val9;
377+
}

0 commit comments

Comments
 (0)