Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP 8.0 | Add utilities to handle the nullsafe object operator #176

Merged
merged 6 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 117 additions & 1 deletion PHPCSUtils/Tokens/Collections.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,13 @@ class Collections
];

/**
* Object operators.
* DEPRECATED: Object operators.
*
* @since 1.0.0-alpha3
*
* @deprecated 1.0.0-alpha4 Use the {@see \PHPCSUtils\Tokens\Collections::objectOperators()}
* method instead.
*
* @var array <int> => <int>
*/
public static $objectOperators = [
Expand Down Expand Up @@ -576,6 +579,119 @@ public static function functionDeclarationTokensBC()
return $tokens;
}

/**
* Object operators.
*
* Note: this is a method, not a property as the `T_NULLSAFE_OBJECT_OPERATOR` token may not exist.
*
* Sister-method to the {@see Collections::objectOperatorsBC()} method.
* This method supports PHP 8.0 and up.
* The {@see Collections::objectOperatorsBC()} method supports PHP < 8.0.
*
* This method can also safely be used if the token collection is only used when looking back
* via `$phpcsFile->findPrevious()` as in that case, a non-backfilled nullsafe object operator
* will still match the "normal" object operator.
*
* @see \PHPCSUtils\Tokens\Collections::objectOperatorsBC() Related method (PHP < 8.0).
*
* @since 1.0.0-alpha4
*
* @return array <int|string> => <int|string>
*/
public static function objectOperators()
{
$tokens = [
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
];

if (\defined('T_NULLSAFE_OBJECT_OPERATOR') === true) {
// PHP 8.0.
$tokens[\T_NULLSAFE_OBJECT_OPERATOR] = \T_NULLSAFE_OBJECT_OPERATOR;
}

return $tokens;
}

/**
* Object operators.
*
* Note: this is a method, not a property as the `T_NULLSAFE_OBJECT_OPERATOR` token may not exist.
*
* Sister-method to the {@see Collections::objectOperators()} method.
* The {@see Collections::objectOperators()} method supports PHP 8.0 and up.
* This method supports PHP < 8.0.
*
* Notable difference:
* - This method accounts for tokens which may be encountered when the `T_NULLSAFE_OBJECT_OPERATOR` token
* doesn't exist.
*
* It is recommended to use the {@see Collections::objectOperators()} method instead of
* this method if a standard does not need to support PHP < 8.0.
*
* The {@see Collections::objectOperators()} method can also safely be used if the token collection
* is only used when looking back via `$phpcsFile->findPrevious()` as in that case, a non-backfilled
* nullsafe object operator will still match the "normal" object operator.
*
* Note: if this method is used, the {@see \PHPCSUtils\Utils\Operators::isNullsafeObjectOperator()}
* method needs to be used on potential nullsafe object operator tokens to verify whether it really
* is a nullsafe object operator or not.
*
* @see \PHPCSUtils\Tokens\Collections::objectOperators() Related method (PHP 8.0+).
* @see \PHPCSUtils\Tokens\Collections::nullsafeObjectOperatorBC() Tokens which can represent a
* nullsafe object operator.
* @see \PHPCSUtils\Utils\Operators::isNullsafeObjectOperator() Nullsafe object operator detection for
* PHP < 8.0.
*
* @since 1.0.0-alpha4
*
* @return array <int|string> => <int|string>
*/
public static function objectOperatorsBC()
{
$tokens = [
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
];

$tokens += self::nullsafeObjectOperatorBC();

return $tokens;
}

/**
* Tokens which can represent the nullsafe object operator.
*
* This method will return the appropriate tokens based on the PHP/PHPCS version used.
*
* Note: this is a method, not a property as the `T_NULLSAFE_OBJECT_OPERATOR` token may not exist.
*
* Note: if this method is used, the {@see \PHPCSUtils\Utils\Operators::isNullsafeObjectOperator()}
* method needs to be used on potential nullsafe object operator tokens to verify whether it really
* is a nullsafe object operator or not.
*
* @see \PHPCSUtils\Utils\Operators::isNullsafeObjectOperator() Nullsafe object operator detection for
* PHP < 8.0.
*
* @since 1.0.0-alpha4
*
* @return array <int|string> => <int|string>
*/
public static function nullsafeObjectOperatorBC()
{
if (\defined('T_NULLSAFE_OBJECT_OPERATOR') === true) {
// PHP 8.0.
return [
\T_NULLSAFE_OBJECT_OPERATOR => \T_NULLSAFE_OBJECT_OPERATOR,
];
}

return [
\T_INLINE_THEN => \T_INLINE_THEN,
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
];
}

/**
* Token types which can be encountered in a parameter type declaration.
*
Expand Down
2 changes: 1 addition & 1 deletion PHPCSUtils/Utils/Namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static function getType(File $phpcsFile, $stackPtr)
+ Tokens::$castTokens
+ Tokens::$blockOpeners
+ Collections::$incrementDecrementOperators
+ Collections::$objectOperators;
+ Collections::objectOperators();

$findAfter[\T_OPEN_CURLY_BRACKET] = \T_OPEN_CURLY_BRACKET;
$findAfter[\T_OPEN_SQUARE_BRACKET] = \T_OPEN_SQUARE_BRACKET;
Expand Down
50 changes: 50 additions & 0 deletions PHPCSUtils/Utils/Operators.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,56 @@ public static function isTypeUnion(File $phpcsFile, $stackPtr)
return false;
}

/**
* Determine whether a token is (part of) a nullsafe object operator.
*
* Helper method for PHP < 8.0 in combination with PHPCS versions in which the
* `T_NULLSAFE_OBJECT_OPERATOR` token is not yet backfilled.
*
* @since 1.0.0-alpha4
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the T_INLINE_THEN or T_OBJECT_OPERATOR
* token in the stack.
*
* @return bool `TRUE` if nullsafe object operator; or `FALSE` otherwise.
*/
public static function isNullsafeObjectOperator(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]) === false) {
return false;
}

// Safeguard in case this method is used on PHP 8 and the nullsafe object operator would be passed.
if ($tokens[$stackPtr]['type'] === 'T_NULLSAFE_OBJECT_OPERATOR') {
return true;
}

if (isset(Collections::nullsafeObjectOperatorBC()[$tokens[$stackPtr]['code']]) === false) {
return false;
}

/*
* Note: not bypassing empty tokens as whitespace and comments are not allowed
* within an operator.
*/
if ($tokens[$stackPtr]['code'] === \T_INLINE_THEN) {
if (isset($tokens[$stackPtr + 1]) && $tokens[$stackPtr + 1]['code'] === \T_OBJECT_OPERATOR) {
return true;
}
}

if ($tokens[$stackPtr]['code'] === \T_OBJECT_OPERATOR) {
if (isset($tokens[$stackPtr - 1]) && $tokens[$stackPtr - 1]['code'] === \T_INLINE_THEN) {
return true;
}
}

// Not a nullsafe object operator token.
return false;
}

/**
* Determine whether a T_MINUS/T_PLUS token is a unary operator.
*
Expand Down
50 changes: 50 additions & 0 deletions Tests/Tokens/Collections/NullsafeObjectOperatorTokensBCTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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\Tests\Tokens\Collections;

use PHPCSUtils\Tokens\Collections;
use PHPUnit\Framework\TestCase;

/**
* Test class.
*
* @covers \PHPCSUtils\Tokens\Collections::nullsafeObjectOperatorBC
*
* @group collections
*
* @since 1.0.0
*/
class NullsafeObjectOperatorBCTest extends TestCase
{

/**
* Test the method.
*
* @return void
*/
public function testNullsafeObjectOperatorBC()
{
$expected = [];
if (\version_compare(\PHP_VERSION_ID, '80000', '>=') === true
) {
$expected = [
\T_NULLSAFE_OBJECT_OPERATOR => \T_NULLSAFE_OBJECT_OPERATOR,
];
} else {
$expected = [
\T_INLINE_THEN => \T_INLINE_THEN,
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
];
}

$this->assertSame($expected, Collections::nullsafeObjectOperatorBC());
}
}
49 changes: 49 additions & 0 deletions Tests/Tokens/Collections/ObjectOperatorsBCTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?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\Tests\Tokens\Collections;

use PHPCSUtils\Tokens\Collections;
use PHPUnit\Framework\TestCase;

/**
* Test class.
*
* @covers \PHPCSUtils\Tokens\Collections::objectOperatorsBC
*
* @group collections
*
* @since 1.0.0
*/
class ObjectOperatorsBCTest extends TestCase
{

/**
* Test the method.
*
* @return void
*/
public function testObjectOperatorsBC()
{
$expected = [
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
];

if (\version_compare(\PHP_VERSION_ID, '80000', '>=') === true
) {
$expected[\T_NULLSAFE_OBJECT_OPERATOR] = \T_NULLSAFE_OBJECT_OPERATOR;
} else {
$expected[\T_INLINE_THEN] = \T_INLINE_THEN;
}

$this->assertSame($expected, Collections::objectOperatorsBC());
}
}
47 changes: 47 additions & 0 deletions Tests/Tokens/Collections/ObjectOperatorsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?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\Tests\Tokens\Collections;

use PHPCSUtils\Tokens\Collections;
use PHPUnit\Framework\TestCase;

/**
* Test class.
*
* @covers \PHPCSUtils\Tokens\Collections::objectOperators
*
* @group collections
*
* @since 1.0.0
*/
class ObjectOperatorsTest extends TestCase
{

/**
* Test the method.
*
* @return void
*/
public function testObjectOperators()
{
$expected = [
\T_OBJECT_OPERATOR => \T_OBJECT_OPERATOR,
\T_DOUBLE_COLON => \T_DOUBLE_COLON,
];

if (\version_compare(\PHP_VERSION_ID, '80000', '>=') === true
) {
$expected[\T_NULLSAFE_OBJECT_OPERATOR] = \T_NULLSAFE_OBJECT_OPERATOR;
}

$this->assertSame($expected, Collections::objectOperators());
}
}
3 changes: 3 additions & 0 deletions Tests/Utils/Namespaces/NamespaceTypeTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ namespace\ClassName::$property++;
/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken3 */
namespace\CONSTANT['key'];

/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken4 */
namespace\functionReturningObj()?->chained();


/* testParseErrorScopedNamespaceDeclaration */
function testScope() {
Expand Down
7 changes: 7 additions & 0 deletions Tests/Utils/Namespaces/NamespaceTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ public function dataNamespaceType()
'operator' => true,
],
],
'namespace-operator-global-namespace-start-of-statement-with-non-confusing-token-4' => [
'/* testNamespaceOperatorGlobalNamespaceStartOfStatementCombiWithNonConfusingToken4 */',
[
'declaration' => false,
'operator' => true,
],
],
'parse-error-scoped-namespace-declaration' => [
'/* testParseErrorScopedNamespaceDeclaration */',
[
Expand Down
Loading