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 7.4: New NewNumericLiteralSeparator sniff #984

Merged
merged 1 commit into from Apr 10, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,86 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2020 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/

namespace PHPCompatibility\Sniffs\Numbers;

use PHP_CodeSniffer_File as File;
use PHP_CodeSniffer\Exceptions\RuntimeException;
use PHPCompatibility\Sniff;
use PHPCSUtils\Utils\Numbers;

/**
* Support for an underscore in numeric literals to visually separate groups of digits
* is available since PHP 7.4.
*
* PHP version 7.4
*
* @link https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.numeric-literal-separator
* @link https://wiki.php.net/rfc/numeric_literal_separator
*
* @since 10.0.0
*/
class NewNumericLiteralSeparatorSniff extends Sniff
{

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 10.0.0
*
* @return array
*/
public function register()
{
return array(
\T_LNUMBER,
\T_DNUMBER,
);
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return int|void Integer stack pointer to skip forward or void to continue
* normal file processing.
*/
public function process(File $phpcsFile, $stackPtr)
{
if ($this->supportsBelow('7.3') === false) {
return;
}

try {
$numberInfo = Numbers::getCompleteNumber($phpcsFile, $stackPtr);
if ($numberInfo['orig_content'] === $numberInfo['content']) {
// Content is the same, i.e. no underscores found, move on.
return;
}

$phpcsFile->addError(
'The use of underscore separators in numeric literals is not supported in PHP 7.3 or lower. Found: %s',
$stackPtr,
'Found',
array($numberInfo['orig_content'])
);

// Skip past the parts we've already taken into account to prevent double reporting.
return ($numberInfo['last_token'] + 1);
} catch (RuntimeException $e) {
// Running on an unsupport PHPCS version.
return;
}
}
}
@@ -0,0 +1,41 @@
<?php

// OK: ordinary numeric sequences.
$a = 1000000000;
$b = 107925284.88;
$discount = 13500;

// OK: Constant name starting with an underscore is perfectly valid.
const _100 = 100;

$a = 100 'test'; // Intentional parse error, not our concern.

// PHP 7.4 numeric sequences with a numeric literal separator.
$threshold = 1_000_000_000; // a billion!
$testValue = 107_925_284.88; // scale is hundreds of millions
$discount = 135_00; // $135, stored as cents

$a = 6.674_083e-11; // float
$a = 6.674_083e+11; // float
$a = 299_792_458; // decimal
$a = 0xCAFE_F00D; // hexadecimal
$a = 0b0101_1111; // binary
$a = 0137_041; // octal

// More test cases.
$a = 1_2.3_4e1_23;

// Invalid use of underscores in numeric sequences.
// Each underscore in a numeric literal must be directly between two digits.
// These below all produce "Parse error: syntax error" in PHP 7.4.
$a = 100_; // trailing
$a = 1__1; // next to underscore
$a = 1_.0; // next to decimal point
$a = 1._0; // next to decimal point
$a = 0x_123; // next to x
$a = 0b_101; // next to b
$a = 1_e2; // next to e
$a = 1e_2; // next to e

// More test cases.
$a = 0xCAFE_F00D_.892;
141 changes: 141 additions & 0 deletions PHPCompatibility/Tests/Numbers/NewNumericLiteralSeparatorUnitTest.php
@@ -0,0 +1,141 @@
<?php
/**
* PHPCompatibility, an external standard for PHP_CodeSniffer.
*
* @package PHPCompatibility
* @copyright 2012-2020 PHPCompatibility Contributors
* @license https://opensource.org/licenses/LGPL-3.0 LGPL3
* @link https://github.com/PHPCompatibility/PHPCompatibility
*/

namespace PHPCompatibility\Tests\Numbers;

use PHPCompatibility\Tests\BaseSniffTest;
use PHPCSUtils\BackCompat\Helper;

/**
* New Numeric Literal Separator Sniff tests
*
* @group newNumericLiteralSeparator
* @group numbers
*
* @covers \PHPCompatibility\Sniffs\Numbers\NewNumericLiteralSeparatorSniff
*
* @since 10.0.0
*/
class NewNumericLiteralSeparatorUnitTest extends BaseSniffTest
{

/**
* Test recognizing numeric literals with underscores correctly.
*
* @dataProvider dataNewNumericLiteralSeparator
*
* @param array $line The line number on which the error should occur.
*
* @return void
*/
public function testNewNumericLiteralSeparator($line)
{
if (version_compare(Helper::getVersion(), '3.5.3', '==')) {
$this->markTestSkipped('PHPCS 3.5.3 is not supported for this sniff');
}

$file = $this->sniffFile(__FILE__, '7.3');
$this->assertError($file, $line, 'The use of underscore separators in numeric literals is not supported in PHP 7.3 or lower. Found:');
}

/**
* Data provider.
*
* @see testNewNumericLiteralSeparator()
*
* @return array
*/
public function dataNewNumericLiteralSeparator()
{
$data = array(
array(14),
array(15),
array(16),
array(18),
array(19),
array(20),
array(21),
array(22),
array(23),
array(26),
);

// The test case on line 39 is half a valid numeric literal with underscore, half parse error.
// The sniff will behave differently on PHP 7.4 vs PHP < 7.4.
if (version_compare(\PHP_VERSION_ID, '70399', '>') || version_compare(Helper::getVersion(), '3.5.3', '<')) {
$data[] = array(41);
}

return $data;
}


/**
* Verify there are no false positives for a PHP version on which this sniff throws errors.
*
* @dataProvider dataNoFalsePositives
*
* @param int $line The line number.
*
* @return void
*/
public function testNoFalsePositives($line)
{
$file = $this->sniffFile(__FILE__, '7.3');
$this->assertNoViolation($file, $line);
}

/**
* Data provider.
*
* @see testNoFalsePositives()
*
* @return array
*/
public function dataNoFalsePositives()
{
$data = array();

// No issues expected on the first 12 lines.
for ($i = 1; $i <= 12; $i++) {
$data[] = array($i);
}

// Parse errors, should be ignored by the sniff.
$data[] = array(31);
$data[] = array(32);
$data[] = array(33);
$data[] = array(34);
$data[] = array(35);
$data[] = array(36);
$data[] = array(37);
$data[] = array(38);

// The test case on line 39 is half a valid numeric literal with underscore, half parse error.
// The sniff will behave differently on PHP 7.4 vs PHP < 7.4.
if (version_compare(\PHP_VERSION_ID, '70399', '<=') && version_compare(Helper::getVersion(), '3.5.3', '>')) {
$data[] = array(41);
}

return $data;
}


/**
* Verify no notices are thrown at all.
*
* @return void
*/
public function testNoViolationsInFileOnValidVersion()
{
$file = $this->sniffFile(__FILE__, '7.4');
$this->assertNoViolation($file);
}
}