@@ -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 /**
0 commit comments