Skip to content

Commit 1eb5b1a

Browse files
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 PHPCSStandards#645
1 parent 060222e commit 1eb5b1a

File tree

3 files changed

+395
-3
lines changed

3 files changed

+395
-3
lines changed

PHPCSUtils/BackCompat/BCFile.php

Lines changed: 167 additions & 3 deletions
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,8 +573,171 @@ public static function getMethodProperties(File $phpcsFile, $stackPtr)
572573
*/
573574
public static function getMemberProperties(File $phpcsFile, $stackPtr)
574575
{
575-
return $phpcsFile->getMemberProperties($stackPtr);
576-
}
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+
}//end if
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 = [
623+
T_PUBLIC => T_PUBLIC,
624+
T_PRIVATE => T_PRIVATE,
625+
T_PROTECTED => T_PROTECTED,
626+
T_STATIC => T_STATIC,
627+
T_VAR => T_VAR,
628+
T_READONLY => T_READONLY,
629+
T_FINAL => T_FINAL,
630+
];
631+
632+
$valid += Tokens::$emptyTokens;
633+
634+
$scope = 'public';
635+
$scopeSpecified = false;
636+
$isStatic = false;
637+
$isReadonly = false;
638+
$isFinal = false;
639+
640+
$startOfStatement = $phpcsFile->findPrevious(
641+
[
642+
T_SEMICOLON,
643+
T_OPEN_CURLY_BRACKET,
644+
T_CLOSE_CURLY_BRACKET,
645+
T_ATTRIBUTE_END,
646+
],
647+
($stackPtr - 1)
648+
);
649+
650+
for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
651+
if (isset($valid[$tokens[$i]['code']]) === false) {
652+
break;
653+
}
654+
655+
switch ($tokens[$i]['code']) {
656+
case T_PUBLIC:
657+
$scope = 'public';
658+
$scopeSpecified = true;
659+
break;
660+
case T_PRIVATE:
661+
$scope = 'private';
662+
$scopeSpecified = true;
663+
break;
664+
case T_PROTECTED:
665+
$scope = 'protected';
666+
$scopeSpecified = true;
667+
break;
668+
case T_STATIC:
669+
$isStatic = true;
670+
break;
671+
case T_READONLY:
672+
$isReadonly = true;
673+
break;
674+
case T_FINAL:
675+
$isFinal = true;
676+
break;
677+
}//end switch
678+
}//end for
679+
680+
$type = '';
681+
$typeToken = false;
682+
$typeEndToken = false;
683+
$nullableType = false;
684+
685+
if ($i < $stackPtr) {
686+
// We've found a type.
687+
$valid = [
688+
T_STRING => T_STRING,
689+
T_CALLABLE => T_CALLABLE,
690+
T_SELF => T_SELF,
691+
T_PARENT => T_PARENT,
692+
T_FALSE => T_FALSE,
693+
T_TRUE => T_TRUE,
694+
T_NULL => T_NULL,
695+
T_NAMESPACE => T_NAMESPACE,
696+
T_NS_SEPARATOR => T_NS_SEPARATOR,
697+
T_TYPE_UNION => T_TYPE_UNION,
698+
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
699+
T_TYPE_OPEN_PARENTHESIS => T_TYPE_OPEN_PARENTHESIS,
700+
T_TYPE_CLOSE_PARENTHESIS => T_TYPE_CLOSE_PARENTHESIS,
701+
];
702+
703+
for ($i; $i < $stackPtr; $i++) {
704+
if ($tokens[$i]['code'] === T_VARIABLE) {
705+
// Hit another variable in a group definition.
706+
break;
707+
}
708+
709+
if ($tokens[$i]['code'] === T_NULLABLE) {
710+
$nullableType = true;
711+
}
712+
713+
if (isset($valid[$tokens[$i]['code']]) === true) {
714+
$typeEndToken = $i;
715+
if ($typeToken === false) {
716+
$typeToken = $i;
717+
}
718+
719+
$type .= $tokens[$i]['content'];
720+
}
721+
}
722+
723+
if ($type !== '' && $nullableType === true) {
724+
$type = '?'.$type;
725+
}
726+
}//end if
727+
728+
return [
729+
'scope' => $scope,
730+
'scope_specified' => $scopeSpecified,
731+
'is_static' => $isStatic,
732+
'is_readonly' => $isReadonly,
733+
'is_final' => $isFinal,
734+
'type' => $type,
735+
'type_token' => $typeToken,
736+
'type_end_token' => $typeEndToken,
737+
'nullable_type' => $nullableType,
738+
];
739+
740+
}//end getMemberProperties()
577741

578742
/**
579743
* Returns the implementation properties of a class.

Tests/BackCompat/BCFile/GetMemberPropertiesTest.inc

Lines changed: 21 additions & 0 deletions
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)