<?php /** * Tests self member references. * * Verifies that : * - self:: is used instead of Self:: * - self:: is used for local static member reference * - self:: is used instead of self :: * * @author Greg Sherwood <gsherwood@squiz.net> * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600) * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ namespace PHP_CodeSniffer\Standards\Squiz\Sniffs\Classes; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Sniffs\AbstractScopeSniff; use PHP_CodeSniffer\Util\Tokens; class SelfMemberReferenceSniff extends AbstractScopeSniff { /** * Constructs a Squiz_Sniffs_Classes_SelfMemberReferenceSniff. */ public function __construct() { parent::__construct([T_CLASS], [T_DOUBLE_COLON]); }//end __construct() /** * Processes the function tokens within the class. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position where the token was found. * @param int $currScope The current scope opener token. * * @return void */ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScope) { $tokens = $phpcsFile->getTokens(); // Determine if this is a double colon which needs to be examined. $conditions = $tokens[$stackPtr]['conditions']; $conditions = array_reverse($conditions, true); foreach ($conditions as $conditionToken => $tokenCode) { if ($tokenCode === T_CLASS || $tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) { break; } } if ($conditionToken !== $currScope) { return; } $calledClassName = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true); if ($calledClassName === false) { // Parse error. return; } if ($tokens[$calledClassName]['code'] === T_SELF) { if ($tokens[$calledClassName]['content'] !== 'self') { $error = 'Must use "self::" for local static member reference; found "%s::"'; $data = [$tokens[$calledClassName]['content']]; $fix = $phpcsFile->addFixableError($error, $calledClassName, 'IncorrectCase', $data); if ($fix === true) { $phpcsFile->fixer->replaceToken($calledClassName, 'self'); } return; } } else if ($tokens[$calledClassName]['code'] === T_STRING) { // If the class is called with a namespace prefix, build fully qualified // namespace calls for both current scope class and requested class. $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($calledClassName - 1), null, true); if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['code'] === T_NS_SEPARATOR) { $declarationName = $this->getDeclarationNameWithNamespace($tokens, $calledClassName); $declarationName = ltrim($declarationName, '\\'); $fullQualifiedClassName = $this->getNamespaceOfScope($phpcsFile, $currScope); if ($fullQualifiedClassName === '\\') { $fullQualifiedClassName = ''; } else { $fullQualifiedClassName .= '\\'; } $fullQualifiedClassName .= $phpcsFile->getDeclarationName($currScope); } else { $declarationName = $phpcsFile->getDeclarationName($currScope); $fullQualifiedClassName = $tokens[$calledClassName]['content']; } if ($declarationName === $fullQualifiedClassName) { // Class name is the same as the current class, which is not allowed. $error = 'Must use "self::" for local static member reference'; $fix = $phpcsFile->addFixableError($error, $calledClassName, 'NotUsed'); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); $currentPointer = ($stackPtr - 1); while ($tokens[$currentPointer]['code'] === T_NS_SEPARATOR || $tokens[$currentPointer]['code'] === T_STRING || isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true ) { if (isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true) { --$currentPointer; continue; } $phpcsFile->fixer->replaceToken($currentPointer, ''); --$currentPointer; } $phpcsFile->fixer->replaceToken($stackPtr, 'self::'); $phpcsFile->fixer->endChangeset(); // Fix potential whitespace issues in the next loop. return; }//end if }//end if }//end if if ($tokens[($stackPtr - 1)]['code'] === T_WHITESPACE) { $found = $tokens[($stackPtr - 1)]['length']; $error = 'Expected 0 spaces before double colon; %s found'; $data = [$found]; $fix = $phpcsFile->addFixableError($error, ($stackPtr - 1), 'SpaceBefore', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = ($stackPtr - 1); $tokens[$i]['code'] === T_WHITESPACE; $i--) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } if ($tokens[($stackPtr + 1)]['code'] === T_WHITESPACE) { $found = $tokens[($stackPtr + 1)]['length']; $error = 'Expected 0 spaces after double colon; %s found'; $data = [$found]; $fix = $phpcsFile->addFixableError($error, ($stackPtr - 1), 'SpaceAfter', $data); if ($fix === true) { $phpcsFile->fixer->beginChangeset(); for ($i = ($stackPtr + 1); $tokens[$i]['code'] === T_WHITESPACE; $i++) { $phpcsFile->fixer->replaceToken($i, ''); } $phpcsFile->fixer->endChangeset(); } } }//end processTokenWithinScope() /** * Processes a token that is found within the scope that this test is * listening to. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position in the stack where this * token was found. * * @return void */ protected function processTokenOutsideScope(File $phpcsFile, $stackPtr) { }//end processTokenOutsideScope() /** * Returns the declaration names for classes/interfaces/functions with a namespace. * * @param array $tokens Token stack for this file * @param int $stackPtr The position where the namespace building will start. * * @return string */ protected function getDeclarationNameWithNamespace(array $tokens, $stackPtr) { $nameParts = []; $currentPointer = $stackPtr; while ($tokens[$currentPointer]['code'] === T_NS_SEPARATOR || $tokens[$currentPointer]['code'] === T_STRING || isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true ) { if (isset(Tokens::$emptyTokens[$tokens[$currentPointer]['code']]) === true) { --$currentPointer; continue; } $nameParts[] = $tokens[$currentPointer]['content']; --$currentPointer; } $nameParts = array_reverse($nameParts); return implode('', $nameParts); }//end getDeclarationNameWithNamespace() /** * Returns the namespace declaration of a file. * * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found. * @param int $stackPtr The position where the search for the * namespace declaration will start. * * @return string */ protected function getNamespaceOfScope(File $phpcsFile, $stackPtr) { $namespace = '\\'; $namespaceDeclaration = $phpcsFile->findPrevious(T_NAMESPACE, $stackPtr); if ($namespaceDeclaration !== false) { $endOfNamespaceDeclaration = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET], $namespaceDeclaration); $namespace = $this->getDeclarationNameWithNamespace( $phpcsFile->getTokens(), ($endOfNamespaceDeclaration - 1) ); } return $namespace; }//end getNamespaceOfScope() }//end class