-
-
Notifications
You must be signed in to change notification settings - Fork 190
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
PHPCompatibility/Sniffs/Classes/ForbiddenExtendingFinalPHPClassSniff.php
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,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 | ||
); | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
PHPCompatibility/Tests/Classes/ForbiddenExtendingFinalPHPClassUnitTest.inc
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,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 {}; | ||
} |
110 changes: 110 additions & 0 deletions
110
PHPCompatibility/Tests/Classes/ForbiddenExtendingFinalPHPClassUnitTest.php
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,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); | ||
} | ||
} |