Skip to content

Commit

Permalink
Merge 88735be into 567d785
Browse files Browse the repository at this point in the history
  • Loading branch information
jrfnl committed May 10, 2024
2 parents 567d785 + 88735be commit 1643439
Show file tree
Hide file tree
Showing 21 changed files with 1,828 additions and 0 deletions.
240 changes: 240 additions & 0 deletions PHPCSUtils/Utils/ObjectDeclarations.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Internal\Cache;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\FunctionDeclarations;
use PHPCSUtils\Utils\GetTokensAsString;

/**
Expand Down Expand Up @@ -356,4 +358,242 @@ private static function findNames(File $phpcsFile, $stackPtr, $keyword, array $a

return $names;
}

/**
* Retrieve all constants declared in an OO structure.
*
* @since 1.1.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The stack position of the OO keyword.
*
* @return array<string, int> Array with names of the found constants as keys and the stack pointers
* to the T_CONST token for each constant as values.
* If no constants are found or a parse error is encountered,
* an empty array is returned.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token doesn't exist or
* is not an OO keyword token.
*/
public static function getDeclaredConstants(File $phpcsFile, $stackPtr)
{
return self::analyzeOOStructure($phpcsFile, $stackPtr)['constants'];
}

/**
* Retrieve all cases declared in an enum.
*
* @since 1.1.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The stack position of the OO keyword.
*
* @return array<string, int> Array with names of the found cases as keys and the stack pointers
* to the T_ENUM_CASE token for each case as values.
* If no cases are found or a parse error is encountered,
* an empty array is returned.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token doesn't exist or
* is not a T_ENUM token.
*/
public static function getDeclaredEnumCases(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_ENUM) {
throw new RuntimeException('$stackPtr must be of type T_ENUM');
}

return self::analyzeOOStructure($phpcsFile, $stackPtr)['cases'];
}

/**
* Retrieve all properties declared in an OO structure.
*
* Note: interfaces and enums cannot contain properties. This method does not take this into
* account to allow sniffs to flag this kind of incorrect PHP code.
*
* @since 1.1.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The stack position of the OO keyword.
*
* @return array<string, int> Array with names of the found properties as keys and the stack pointers
* to the T_VARIABLE token for each property as values.
* If no properties are found or a parse error is encountered,
* an empty array is returned.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token doesn't exist or
* is not an OO keyword token.
*/
public static function getDeclaredProperties(File $phpcsFile, $stackPtr)
{
return self::analyzeOOStructure($phpcsFile, $stackPtr)['properties'];
}

/**
* Retrieve all methods declared in an OO structure.
*
* @since 1.1.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The stack pointer to the OO keyword.
*
* @return array<string, int> Array with names of the found methods as keys and the stack pointers
* to the T_FUNCTION keyword for each method as values.
* If no methods are found or a parse error is encountered,
* an empty array is returned.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token doesn't exist or
* is not an OO keyword token.
*/
public static function getDeclaredMethods(File $phpcsFile, $stackPtr)
{
return self::analyzeOOStructure($phpcsFile, $stackPtr)['methods'];
}

/**
* Retrieve all constants, cases, properties and methods in an OO structure.
*
* @since 1.1.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
* @param int $stackPtr The stack position of the OO keyword.
*
* @return array<string, array<string, int>> Multi-dimensional array with four keys:
* - "constants"
* - "cases"
* - "properties"
* - "methods"
* Each index holds an associative array with the name of the "thing"
* as the key and the stack pointer to the related token as the value.
*
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token doesn't exist or
* is not an OO keyword token.
*/
private static function analyzeOOStructure(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();

if (isset($tokens[$stackPtr]) === false
|| isset(Tokens::$ooScopeTokens[$tokens[$stackPtr]['code']]) === false
) {
throw new RuntimeException(
'$stackPtr must be of type T_CLASS, T_ANON_CLASS, T_INTERFACE, T_TRAIT or T_ENUM'
);
}

// Set defaults.
$found = [
'constants' => [],
'cases' => [],
'properties' => [],
'methods' => [],
];

if (isset($tokens[$stackPtr]['scope_opener'], $tokens[$stackPtr]['scope_closer']) === false) {
return $found;
}

if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) {
return Cache::get($phpcsFile, __METHOD__, $stackPtr);
}

for ($i = ($tokens[$stackPtr]['scope_opener'] + 1); $i < $tokens[$stackPtr]['scope_closer']; $i++) {
// Skip over potentially large docblocks.
if (isset($tokens[$i]['comment_closer']) === true) {
$i = $tokens[$i]['comment_closer'];
continue;
}

// Skip over attributes.
if (isset($tokens[$i]['attribute_closer']) === true) {
$i = $tokens[$i]['attribute_closer'];
continue;
}

// Skip over trait imports with conflict resolution.
if ($tokens[$i]['code'] === \T_USE
&& isset($tokens[$i]['scope_closer']) === true
) {
$i = $tokens[$i]['scope_closer'];
continue;
}

// Defensive coding against parse errors.
if ($tokens[$i]['code'] === \T_CLOSURE
&& isset($tokens[$i]['scope_closer']) === true
) {
$i = $tokens[$i]['scope_closer'];
continue;
}

switch ($tokens[$i]['code']) {
case \T_CONST:
$assignmentPtr = $phpcsFile->findNext([\T_EQUAL, \T_SEMICOLON, \T_CLOSE_CURLY_BRACKET], ($i + 1));
if ($assignmentPtr === false || $tokens[$assignmentPtr]['code'] !== \T_EQUAL) {
// Probably a parse error. Ignore.
continue 2;
}

$namePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentPtr - 1), ($i + 1), true);
if ($namePtr === false || $tokens[$namePtr]['code'] !== \T_STRING) {
// Probably a parse error. Ignore.
continue 2;
}

$found['constants'][$tokens[$namePtr]['content']] = $i;

// Skip to the assignment pointer, no need to double walk.
$i = $assignmentPtr;
break;

case \T_ENUM_CASE:
$namePtr = $phpcsFile->findNext(Tokens::$emptyTokens, ($i + 1), null, true);
if ($namePtr === false || $tokens[$namePtr]['code'] !== \T_STRING) {
// Probably a parse error. Ignore.
continue 2;
}

$name = $tokens[$namePtr]['content'];
$found['cases'][$name] = $i;

// Skip to the name pointer, no need to double walk.
$i = $namePtr;
break;

case \T_VARIABLE:
$name = $tokens[$i]['content'];
$found['properties'][$name] = $i;
break;

case \T_FUNCTION:
$name = self::getName($phpcsFile, $i);
if (\is_string($name) && $name !== '') {
$found['methods'][$name] = $i;

if (\strtolower($name) === '__construct') {
// Check for constructor property promotion.
$parameters = FunctionDeclarations::getParameters($phpcsFile, $i);
foreach ($parameters as $param) {
if (isset($param['property_visibility'])) {
$found['properties'][$param['name']] = $param['token'];
}
}
}
}

if (isset($tokens[$i]['scope_closer']) === true) {
// Skip over the contents of the method, including the parameters.
$i = $tokens[$i]['scope_closer'];
} elseif (isset($tokens[$i]['parenthesis_closer']) === true) {
// Skip over the contents of an abstract/interface method, including the parameters.
$i = $tokens[$i]['parenthesis_closer'];
}
break;
}
}

Cache::set($phpcsFile, __METHOD__, $stackPtr, $found);
return $found;
}
}
Loading

0 comments on commit 1643439

Please sign in to comment.