Skip to content

Commit 3421f81

Browse files
committed
✨ New Tokens\TokenHelper class
As of PHPUnit 9.3, PHPUnit started using PHP-Parser as a basis to calculate code coverage. The problem with this is that PHP-Parser backfills new PHP native tokens, while the code in this repo contains logic which checks for the existence of tokens using `defined()`. That logic breaks with PHP-Parser in play. An external PHPCS standard may also be using PHP-Parser, but as Parser doesn't fix the tokenization of files, the tokens polyfilled by PHP-Parser would still cause interference. To work round this, I'm introducing a `TokenHelper::tokenExists()` method which can distinguish tokens defined by PHP and PHPCS from tokens defined by PHP-Parser. This should solve the issue in most cases, though in edge-cases, PHP-Parser could still prevent the PHPCS native `Tokenizer\PHP` class from providing polyfills when it should. For external standards which want to use PHPUnit 9.3+ for calculating code coverage, warming the cache is also still recommended, though it shouldn't necessarily be _needed_ anymore if the standard implements the use of the `TokenHelper::tokenExists()` method in all the appropriate places. Includes unit tests. Refs: * sebastianbergmann/php-code-coverage#798 * https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Lexer.php Note: while `Helper` would have been the more intuitive name for the class, as there is also a `BackCompat\Helper` class, this would mean use aliases would need to be used a lot, so instead, I've elected to name the class `TokenHelper`.
1 parent a276f34 commit 3421f81

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

PHPCSUtils/Tokens/TokenHelper.php

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019-2020 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Tokens;
12+
13+
/**
14+
* Helpers for working with tokens.
15+
*
16+
* @since 1.0.0
17+
*/
18+
class TokenHelper
19+
{
20+
21+
/**
22+
* Check whether a PHP native token exists (for real).
23+
*
24+
* Under normal circumstances, checking whether a token exists (either defined by PHP or by PHPCS)
25+
* is as straight-forward as running `defined('T_TOKEN_NAME')`.
26+
*
27+
* Unfortunately, this doesn't work in all circumstances, most notably, if an external standard
28+
* also uses PHP-Parser or when code coverage is run on a standard using PHPUnit >= 9.3 (which uses PHP-Parser),
29+
* this logic breaks because PHP-Parser also polyfills tokens.
30+
* This method takes potentially polyfilled tokens from PHP-Parser into account and will regard the token
31+
* as undefined if it was declared by PHP-Parser.
32+
*
33+
* Note: this method only _needs_ to be used for PHP native tokens, not for PHPCS specific tokens.
34+
* Also, realistically, it only needs to be used for tokens introduced in PHP in recent versions (PHP 7.4 and up).
35+
* Having said that, the method _will_ also work correctly when a name of a PHPCS native token is passed or
36+
* of an older PHP native token.
37+
*
38+
* {@internal PHP native tokens have a positive integer value. PHPCS polyfilled tokens are strings.
39+
* PHP-Parser polyfilled tokens will always have a negative integer value < 0, which is how
40+
* these are filtered out.}
41+
*
42+
* @link https://github.com/sebastianbergmann/php-code-coverage/issues/798
43+
* @link https://github.com/nikic/PHP-Parser/blob/master/lib/PhpParser/Lexer.php
44+
*
45+
* @since 1.0.0-alpha4
46+
*
47+
* @param string $name The token name.
48+
*
49+
* @return bool
50+
*/
51+
public static function tokenExists($name)
52+
{
53+
return (\defined($name) && (\is_int(\constant($name)) === false || \constant($name) > 0));
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
/**
3+
* PHPCSUtils, utility functions and classes for PHP_CodeSniffer sniff developers.
4+
*
5+
* @package PHPCSUtils
6+
* @copyright 2019-2020 PHPCSUtils Contributors
7+
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
8+
* @link https://github.com/PHPCSStandards/PHPCSUtils
9+
*/
10+
11+
namespace PHPCSUtils\Tests\Tokens\TokenHelper;
12+
13+
use PHPCSUtils\BackCompat\Helper;
14+
use PHPCSUtils\Tokens\TokenHelper;
15+
use PHPUnit\Framework\TestCase;
16+
17+
/**
18+
* Test class.
19+
*
20+
* @covers \PHPCSUtils\Tokens\TokenHelper::tokenExists
21+
*
22+
* @since 1.0.0
23+
*/
24+
class TokenExistsTest extends TestCase
25+
{
26+
27+
/**
28+
* Add select constants to allow testing this method.
29+
*
30+
* @beforeClass
31+
*
32+
* @return void
33+
*/
34+
public static function setUpConstants()
35+
{
36+
\define('T_FAKETOKEN', -5);
37+
}
38+
39+
/**
40+
* Test the method.
41+
*
42+
* @dataProvider dataTokenExists
43+
*
44+
* @param string $name Token name.
45+
* @param bool $expected Expected function return value.
46+
*
47+
* @return void
48+
*/
49+
public function testTokenExists($name, $expected)
50+
{
51+
$this->assertSame($expected, TokenHelper::tokenExists($name));
52+
}
53+
54+
/**
55+
* Data provider.
56+
*
57+
* @return array
58+
*/
59+
public function dataTokenExists()
60+
{
61+
$phpcsVersion = Helper::getVersion();
62+
63+
return [
64+
'Token which doesn\'t exist either way' => [
65+
'name' => 'T_DOESNOTEXIST',
66+
'expected' => false,
67+
],
68+
'PHP native token which always exists' => [
69+
'name' => 'T_FUNCTION',
70+
'expected' => true,
71+
],
72+
'PHPCS native token which always exists (in the PHPCS versions supported)' => [
73+
'name' => 'T_CLOSURE',
74+
'expected' => true,
75+
],
76+
'PHP native token which may not exist - PHP 7.4 T_FN' => [
77+
'name' => 'T_FN',
78+
'expected' => (\version_compare($phpcsVersion, '3.5.3', '>=')
79+
|| \version_compare(\PHP_VERSION_ID, '70399', '>=')),
80+
],
81+
'PHP native token which may not exist - PHP 8.0 T_NULLSAFE_OBJECT_OPERATOR' => [
82+
'name' => 'T_NULLSAFE_OBJECT_OPERATOR',
83+
'expected' => (\version_compare($phpcsVersion, '3.5.7', '>=')
84+
|| \version_compare(\PHP_VERSION_ID, '79999', '>=')),
85+
],
86+
'PHP native token which may not exist - PHP 8.1 T_ENUM' => [
87+
'name' => 'T_ENUM',
88+
'expected' => (\version_compare($phpcsVersion, '3.7.0', '>=')
89+
|| \version_compare(\PHP_VERSION_ID, '80099', '>=')),
90+
],
91+
'Mocked polyfilled token' => [
92+
'name' => 'T_FAKETOKEN',
93+
'expected' => false,
94+
],
95+
];
96+
}
97+
}

0 commit comments

Comments
 (0)