-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ New
PHPCSUtils\Utils\Constants
class
... to contain utilities methods for analysing constants declared using the `const` keyword. In the future, the scope of the methods might be expanded to also covered constants declared using `define()`, but that's for later. Initially, the class comes with the following method: * `getProperties(File $phpcsFile, $stackPtr): array` to retrieve an array of information about a constant declaration, like the visibility, whether visibility was explicitly declared, whether the constant was declared as final, what the type is for the constant etc. Includes extensive unit tests.
- Loading branch information
Showing
9 changed files
with
1,285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2020 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
*/ | ||
|
||
namespace PHPCSUtils\Utils; | ||
|
||
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\Scopes; | ||
|
||
/** | ||
* Utility functions for use when examining constants declared using the "const" keyword. | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class Constants | ||
{ | ||
|
||
/** | ||
* Retrieve the visibility and implementation properties of an OO constant. | ||
* | ||
* @since 1.1.0 | ||
* | ||
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. | ||
* @param int $stackPtr The position in the stack of the `T_CONST` token | ||
* to acquire the properties for. | ||
* | ||
* @return array<string, string|int|bool> Array with information about the constant declaration. | ||
* The format of the return value is: | ||
* ```php | ||
* array( | ||
* 'scope' => string, // Public, private, or protected. | ||
* 'scope_token' => integer|false, // The stack pointer to the scope keyword or | ||
* // FALSE if the scope was not explicitly specified. | ||
* 'is_final' => boolean, // TRUE if the final keyword was found. | ||
* 'final_token' => integer|false, // The stack pointer to the final keyword | ||
* // or FALSE if the const is not declared final. | ||
* 'type' => string, // The type of the const (empty if no type specified). | ||
* 'type_token' => integer|false, // The stack pointer to the start of the type | ||
* // or FALSE if there is no type. | ||
* 'type_end_token' => integer|false, // The stack pointer to the end of the type | ||
* // or FALSE if there is no type. | ||
* 'nullable_type' => boolean, // TRUE if the type is preceded by the | ||
* // nullability operator. | ||
* 'name_token' => integer, // The stack pointer to the constant name. | ||
* // Note: for group declarations this points to the | ||
* // name of the first constant. | ||
* 'equal_token' => integer, // The stack pointer to the equal sign. | ||
* // Note: for group declarations this points to the | ||
* // equal sign of the first constant. | ||
* ); | ||
* ``` | ||
* | ||
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not a `T_CONST` token. | ||
* @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified position is not an OO constant. | ||
*/ | ||
public static function getProperties(File $phpcsFile, $stackPtr) | ||
{ | ||
$tokens = $phpcsFile->getTokens(); | ||
|
||
if (isset($tokens[$stackPtr]) === false || $tokens[$stackPtr]['code'] !== \T_CONST) { | ||
throw new RuntimeException('$stackPtr must be of type T_CONST'); | ||
} | ||
|
||
if (Scopes::isOOConstant($phpcsFile, $stackPtr) === false) { | ||
throw new RuntimeException('$stackPtr is not an OO constant'); | ||
} | ||
|
||
if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { | ||
return Cache::get($phpcsFile, __METHOD__, $stackPtr); | ||
} | ||
|
||
$assignmentPtr = $phpcsFile->findNext([\T_EQUAL, \T_SEMICOLON, \T_CLOSE_CURLY_BRACKET], ($stackPtr + 1)); | ||
if ($assignmentPtr === false || $tokens[$assignmentPtr]['code'] !== \T_EQUAL) { | ||
// Probably a parse error. Don't cache the result. | ||
throw new RuntimeException('$stackPtr is not an OO constant'); | ||
} | ||
|
||
$namePtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($assignmentPtr - 1), ($stackPtr + 1), true); | ||
|
||
$returnValue = [ | ||
'scope' => 'public', | ||
'scope_token' => false, | ||
'is_final' => false, | ||
'final_token' => false, | ||
'type' => '', | ||
'type_token' => false, | ||
'type_end_token' => false, | ||
'nullable_type' => false, | ||
'name_token' => $namePtr, | ||
'equal_token' => $assignmentPtr, | ||
]; | ||
|
||
for ($i = ($stackPtr - 1);; $i--) { | ||
// Skip over potentially large docblocks. | ||
if ($tokens[$i]['code'] === \T_DOC_COMMENT_CLOSE_TAG | ||
&& isset($tokens[$i]['comment_opener']) | ||
) { | ||
$i = $tokens[$i]['comment_opener']; | ||
continue; | ||
} | ||
|
||
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { | ||
continue; | ||
} | ||
|
||
switch ($tokens[$i]['code']) { | ||
case \T_PUBLIC: | ||
$returnValue['scope'] = 'public'; | ||
$returnValue['scope_token'] = $i; | ||
break; | ||
|
||
case \T_PROTECTED: | ||
$returnValue['scope'] = 'protected'; | ||
$returnValue['scope_token'] = $i; | ||
break; | ||
|
||
case \T_PRIVATE: | ||
$returnValue['scope'] = 'private'; | ||
$returnValue['scope_token'] = $i; | ||
break; | ||
|
||
case \T_FINAL: | ||
$returnValue['is_final'] = true; | ||
$returnValue['final_token'] = $i; | ||
break; | ||
|
||
default: | ||
// Any other token means that the start of the statement has been reached. | ||
break 2; | ||
} | ||
} | ||
|
||
$type = ''; | ||
$typeToken = false; | ||
$typeEndToken = false; | ||
$constantTypeTokens = Collections::constantTypeTokens(); | ||
|
||
// Now, let's check for a type. | ||
for ($i = ($stackPtr + 1); $i < $namePtr; $i++) { | ||
if (isset(Tokens::$emptyTokens[$tokens[$i]['code']]) === true) { | ||
continue; | ||
} | ||
|
||
if ($tokens[$i]['code'] === \T_NULLABLE) { | ||
$returnValue['nullable_type'] = true; | ||
continue; | ||
} | ||
|
||
if (isset($constantTypeTokens[$tokens[$i]['code']]) === true) { | ||
$typeEndToken = $i; | ||
if ($typeToken === false) { | ||
$typeToken = $i; | ||
} | ||
|
||
$type .= $tokens[$i]['content']; | ||
} | ||
} | ||
|
||
if ($type !== '' && $returnValue['nullable_type'] === true) { | ||
$type = '?' . $type; | ||
} | ||
|
||
$returnValue['type'] = $type; | ||
$returnValue['type_token'] = $typeToken; | ||
$returnValue['type_end_token'] = $typeEndToken; | ||
|
||
Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue); | ||
return $returnValue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?php | ||
|
||
class LiveCodingParseError { | ||
/* testParseErrorLiveCoding */ | ||
final const false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2024 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
*/ | ||
|
||
namespace PHPCSUtils\Tests\Utils\Constants; | ||
|
||
use PHPCSUtils\TestUtils\UtilityMethodTestCase; | ||
use PHPCSUtils\Utils\Constants; | ||
|
||
/** | ||
* Tests for the \PHPCSUtils\Utils\Constants::getProperties method. | ||
* | ||
* @covers \PHPCSUtils\Utils\Constants::getProperties | ||
* | ||
* @group constants | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class GetPropertiesParseError1Test extends UtilityMethodTestCase | ||
{ | ||
|
||
/** | ||
* Test receiving an exception when encountering a specific parse error. | ||
* | ||
* @return void | ||
*/ | ||
public function testParseError() | ||
{ | ||
$this->expectPhpcsException('$stackPtr is not an OO constant'); | ||
|
||
$const = $this->getTargetToken('/* testParseErrorLiveCoding */', \T_CONST); | ||
Constants::getProperties(self::$phpcsFile, $const); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?php | ||
|
||
class LiveCodingParseError { | ||
/* testParseErrorLiveCoding */ | ||
private const ?int TYPED_INT = 0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2024 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
*/ | ||
|
||
namespace PHPCSUtils\Tests\Utils\Constants; | ||
|
||
use PHPCSUtils\TestUtils\UtilityMethodTestCase; | ||
use PHPCSUtils\Utils\Constants; | ||
|
||
/** | ||
* Tests for the \PHPCSUtils\Utils\Constants::getProperties method. | ||
* | ||
* @covers \PHPCSUtils\Utils\Constants::getProperties | ||
* | ||
* @group constants | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class GetPropertiesParseError2Test extends UtilityMethodTestCase | ||
{ | ||
|
||
/** | ||
* Test receiving an exception when encountering a specific parse error. | ||
* | ||
* @return void | ||
*/ | ||
public function testParseError() | ||
{ | ||
$this->expectPhpcsException('$stackPtr is not an OO constant'); | ||
|
||
$const = $this->getTargetToken('/* testParseErrorLiveCoding */', \T_CONST); | ||
Constants::getProperties(self::$phpcsFile, $const); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?php | ||
|
||
class LiveCodingParseError { | ||
/* testParseErrorMissingName */ | ||
private const = 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
<?php | ||
/** | ||
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers. | ||
* | ||
* @package PHPCSUtils | ||
* @copyright 2019-2024 PHPCSUtils Contributors | ||
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3 | ||
* @link https://github.com/PHPCSStandards/PHPCSUtils | ||
*/ | ||
|
||
namespace PHPCSUtils\Tests\Utils\Constants; | ||
|
||
use PHPCSUtils\TestUtils\UtilityMethodTestCase; | ||
use PHPCSUtils\Utils\Constants; | ||
|
||
/** | ||
* Tests for the \PHPCSUtils\Utils\Constants::getProperties method. | ||
* | ||
* @covers \PHPCSUtils\Utils\Constants::getProperties | ||
* | ||
* @group constants | ||
* | ||
* @since 1.1.0 | ||
*/ | ||
final class GetPropertiesParseError3Test extends UtilityMethodTestCase | ||
{ | ||
|
||
/** | ||
* Test the getProperties() method returns false for the name pointer, when the name is missing. | ||
* | ||
* @return void | ||
*/ | ||
public function testGetProperties() | ||
{ | ||
$const = $this->getTargetToken('/* testParseErrorMissingName */', \T_CONST); | ||
$expected = [ | ||
'scope' => 'private', | ||
'scope_token' => ($const - 2), | ||
'is_final' => false, | ||
'final_token' => false, | ||
'type' => '', | ||
'type_token' => false, | ||
'type_end_token' => false, | ||
'nullable_type' => false, | ||
'name_token' => false, | ||
'equal_token' => ($const + 2), | ||
]; | ||
$result = Constants::getProperties(self::$phpcsFile, $const); | ||
|
||
$this->assertSame($expected, $result); | ||
} | ||
} |
Oops, something went wrong.