diff --git a/src/Files/File.php b/src/Files/File.php index 3e1409c58a..29b15c0e08 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -873,9 +873,13 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s $parts = explode('.', $code); if ($parts[0] === 'Internal') { // An internal message. - $listenerCode = Common::getSniffCode($this->activeListener); - $sniffCode = $code; - $checkCodes = [$sniffCode]; + $listenerCode = ''; + if ($this->activeListener !== '') { + $listenerCode = Common::getSniffCode($this->activeListener); + } + + $sniffCode = $code; + $checkCodes = [$sniffCode]; } else { if ($parts[0] !== $code) { // The full message code has been passed in. diff --git a/src/Util/Common.php b/src/Util/Common.php index ca00d23898..af4aba69fd 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -9,6 +9,7 @@ namespace PHP_CodeSniffer\Util; +use InvalidArgumentException; use Phar; class Common @@ -529,25 +530,41 @@ public static function suggestType($varType) * @param string $sniffClass The fully qualified sniff class name. * * @return string + * + * @throws \InvalidArgumentException When $sniffClass is not a non-empty string. + * @throws \InvalidArgumentException When $sniffClass is not a FQN for a sniff(test) class. */ public static function getSniffCode($sniffClass) { - $parts = explode('\\', $sniffClass); - $sniff = array_pop($parts); + if (is_string($sniffClass) === false || $sniffClass === '') { + throw new InvalidArgumentException('The $sniffClass parameter must be a non-empty string'); + } + + $parts = explode('\\', $sniffClass); + $partsCount = count($parts); + if ($partsCount < 4) { + throw new InvalidArgumentException( + 'The $sniffClass parameter was not passed a fully qualified sniff(test) class name. Received: '.$sniffClass + ); + } + + $sniff = $parts[($partsCount - 1)]; if (substr($sniff, -5) === 'Sniff') { // Sniff class name. $sniff = substr($sniff, 0, -5); - } else { + } else if (substr($sniff, -8) === 'UnitTest') { // Unit test class name. $sniff = substr($sniff, 0, -8); + } else { + throw new InvalidArgumentException( + 'The $sniffClass parameter was not passed a fully qualified sniff(test) class name. Received: '.$sniffClass + ); } - $category = array_pop($parts); - $sniffDir = array_pop($parts); - $standard = array_pop($parts); - $code = $standard.'.'.$category.'.'.$sniff; - return $code; + $standard = $parts[($partsCount - 4)]; + $category = $parts[($partsCount - 2)]; + return $standard.'.'.$category.'.'.$sniff; }//end getSniffCode() diff --git a/tests/Core/Util/Common/GetSniffCodeTest.php b/tests/Core/Util/Common/GetSniffCodeTest.php new file mode 100644 index 0000000000..d27593416d --- /dev/null +++ b/tests/Core/Util/Common/GetSniffCodeTest.php @@ -0,0 +1,172 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Util\Common; + +use PHP_CodeSniffer\Util\Common; +use PHPUnit\Framework\TestCase; + +/** + * Tests for the \PHP_CodeSniffer\Util\Common::getSniffCode() method. + * + * @covers \PHP_CodeSniffer\Util\Common::getSniffCode + */ +final class GetSniffCodeTest extends TestCase +{ + + + /** + * Test receiving an expected exception when the $sniffClass parameter is not passed a string value or is passed an empty string. + * + * @param string $input NOT a fully qualified sniff class name. + * + * @dataProvider dataGetSniffCodeThrowsExceptionOnInvalidInput + * + * @return void + */ + public function testGetSniffCodeThrowsExceptionOnInvalidInput($input) + { + $exception = 'InvalidArgumentException'; + $message = 'The $sniffClass parameter must be a non-empty string'; + + if (method_exists($this, 'expectException') === true) { + // PHPUnit 5+. + $this->expectException($exception); + $this->expectExceptionMessage($message); + } else { + // PHPUnit 4. + $this->setExpectedException($exception, $message); + } + + Common::getSniffCode($input); + + }//end testGetSniffCodeThrowsExceptionOnInvalidInput() + + + /** + * Data provider. + * + * @see testGetSniffCodeThrowsExceptionOnInvalidInput() + * + * @return array> + */ + public static function dataGetSniffCodeThrowsExceptionOnInvalidInput() + { + return [ + 'Class name is not a string' => [true], + 'Class name is empty' => [''], + ]; + + }//end dataGetSniffCodeThrowsExceptionOnInvalidInput() + + + /** + * Test receiving an expected exception when the $sniffClass parameter is not passed a value which + * could be a fully qualified sniff(test) class name. + * + * @param string $input String input which can not be a fully qualified sniff(test) class name. + * + * @dataProvider dataGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass + * + * @return void + */ + public function testGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass($input) + { + $exception = 'InvalidArgumentException'; + $message = 'The $sniffClass parameter was not passed a fully qualified sniff(test) class name. Received:'; + + if (method_exists($this, 'expectException') === true) { + // PHPUnit 5+. + $this->expectException($exception); + $this->expectExceptionMessage($message); + } else { + // PHPUnit 4. + $this->setExpectedException($exception, $message); + } + + Common::getSniffCode($input); + + }//end testGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass() + + + /** + * Data provider. + * + * @see testGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass() + * + * @return array> + */ + public static function dataGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass() + { + return [ + 'Unqualified class name' => ['ClassName'], + 'Fully qualified class name, not enough parts' => ['Fully\\Qualified\\ClassName'], + 'Fully qualified class name, doesn\'t end on Sniff or UnitTest' => ['Fully\\Sniffs\\Qualified\\ClassName'], + ]; + + }//end dataGetSniffCodeThrowsExceptionOnInputWhichIsNotASniffTestClass() + + + /** + * Test transforming a sniff class name to a sniff code. + * + * @param string $fqnClass A fully qualified sniff class name. + * @param string $expected Expected function output. + * + * @dataProvider dataGetSniffCode + * + * @return void + */ + public function testGetSniffCode($fqnClass, $expected) + { + $this->assertSame($expected, Common::getSniffCode($fqnClass)); + + }//end testGetSniffCode() + + + /** + * Data provider. + * + * @see testGetSniffCode() + * + * @return array> + */ + public static function dataGetSniffCode() + { + return [ + 'PHPCS native sniff' => [ + 'fqnClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Arrays\\ArrayIndentSniff', + 'expected' => 'Generic.Arrays.ArrayIndent', + ], + 'Class is a PHPCS native test class' => [ + 'fqnClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Tests\\Arrays\\ArrayIndentUnitTest', + 'expected' => 'Generic.Arrays.ArrayIndent', + ], + 'Sniff in external standard without namespace prefix' => [ + 'fqnClass' => 'MyStandard\\Sniffs\\PHP\\MyNameSniff', + 'expected' => 'MyStandard.PHP.MyName', + ], + 'Test in external standard without namespace prefix' => [ + 'fqnClass' => 'MyStandard\\Tests\\PHP\\MyNameSniff', + 'expected' => 'MyStandard.PHP.MyName', + ], + 'Sniff in external standard with namespace prefix' => [ + 'fqnClass' => 'Vendor\\Package\\MyStandard\\Sniffs\\Category\\AnalyzeMeSniff', + 'expected' => 'MyStandard.Category.AnalyzeMe', + ], + 'Test in external standard with namespace prefix' => [ + 'fqnClass' => 'Vendor\\Package\\MyStandard\\Tests\\Category\\AnalyzeMeUnitTest', + 'expected' => 'MyStandard.Category.AnalyzeMe', + ], + ]; + + }//end dataGetSniffCode() + + +}//end class