Skip to content

Commit

Permalink
Merge 640e3b2 into 4576a17
Browse files Browse the repository at this point in the history
  • Loading branch information
jrfnl committed Mar 27, 2023
2 parents 4576a17 + 640e3b2 commit dcc729c
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?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\Classes;

use PHPCompatibility\Sniff;
use PHP_CodeSniffer\Files\File;

/**
* Detect extending of PHP native classes which became final at some point.
*
* PHP version 8.0+
*
* @since 10.0.0
*/
class ForbiddenExtendingFinalPHPClassSniff extends Sniff
{

/**
* A list of PHP native classes which became final at some point.
*
* @since 10.0.0
*
* @var array(string => int) Key is the fully qualified classname.
* Value the PHP version in which the class became final.
*/
protected $finalClasses = [
'\__PHP_Incomplete_Class' => '8.0',
];

/**
* Returns an array of tokens this test wants to listen for.
*
* @since 10.0.0
*
* @return array
*/
public function register()
{
// Handle case-insensitivity of class names.
$this->finalClasses = \array_change_key_case($this->finalClasses, \CASE_LOWER);

return [
\T_CLASS,
\T_ANON_CLASS,
];
}

/**
* Processes this test, when one of its tokens is encountered.
*
* @since 10.0.0
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in
* the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$FQClassName = $this->getFQExtendedClassName($phpcsFile, $stackPtr);
if ($FQClassName === '') {
return;
}

$classNameLc = \strtolower($FQClassName);

if (isset($this->finalClasses[$classNameLc]) === false) {
return;
}

if ($this->supportsAbove($this->finalClasses[$classNameLc]) === false) {
return;
}

$data = [
\substr($FQClassName, 1), // Remove global namespace indicator.
$this->finalClasses[$classNameLc],
];

$phpcsFile->addError(
'The build-in class %s is final since PHP %s and cannot be extended',
$stackPtr,
'Found',
$data
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace {
// OK.
class MyClass extends PHP_Incomplete_Class {}
$anonClass = new class extends SomethingElse {};
class NotThePHPNativeClass extends My\NS\__PHP_Incomplete_Class {}

// Extending a final class.
class Incomplete extends __PHP_Incomplete_Class {}
$anonClass = new class extends \__PHP_Incomplete_Class {};
}

namespace MyNamespace {
// OK.
class MyClass extends PHP_Incomplete_Class {}
class NotThePHPNativeClass extends \My\__PHP_Incomplete_Class {}

// Extending a final class.
$anonClass = new class extends \__PHP_Incomplete_Class {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?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\Classes;

use PHPCompatibility\Tests\BaseSniffTest;

/**
* Test the ForbiddenExtendingFinalPHPClass sniff.
*
* @group forbiddenExtendingFinalPHPClass
* @group classes
*
* @covers \PHPCompatibility\Sniffs\Classes\ForbiddenExtendingFinalPHPClassSniff
*
* @since 10.0.0
*/
class ForbiddenExtendingFinalPHPClassUnitTest extends BaseSniffTest
{

/**
* Verify an error is thrown when a PHP native class which became final is being extended.
*
* @dataProvider dataForbiddenExtendingFinalPHPClass
*
* @param string $className Class name.
* @param string $finalIn The PHP version in which the class was made final.
* @param array $line The line number in the test file which apply to this class.
* @param string $okVersion A PHP version in which the class was ok to be extended.
*
* @return void
*/
public function testForbiddenExtendingFinalPHPClass($className, $finalIn, $line, $okVersion)
{
$file = $this->sniffFile(__FILE__, $finalIn);
$this->assertError($file, $line, "The build-in class {$className} is final since PHP {$finalIn} and cannot be extended");

$file = $this->sniffFile(__FILE__, $okVersion);
$this->assertNoViolation($file, $line);
}

/**
* Data provider.
*
* @see testForbiddenExtendingFinalPHPClass()
*
* @return array
*/
public function dataForbiddenExtendingFinalPHPClass()
{
return [
['__PHP_Incomplete_Class', '8.0', 10, '7.4'],
['__PHP_Incomplete_Class', '8.0', 11, '7.4'],
['__PHP_Incomplete_Class', '8.0', 20, '7.4'],
];
}


/**
* Verify the sniff does not throw false positives for valid code.
*
* @dataProvider dataNoFalsePositives
*
* @param int $line The line number.
*
* @return void
*/
public function testNoFalsePositives($line)
{
$file = $this->sniffFile(__FILE__, '8.0');
$this->assertNoViolation($file, $line);
}

/**
* Data provider.
*
* @see testNoFalsePositives()
*
* @return array
*/
public function dataNoFalsePositives()
{
return [
[5],
[6],
[7],
[16],
[17],
];
}


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

0 comments on commit dcc729c

Please sign in to comment.