Skip to content

Ruleset: handle invalid sniffs more graciously #873

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

Merged
merged 1 commit into from
Mar 17, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/Ruleset.php
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,24 @@ public function registerSniffs($files, $restrictions, $exclusions)
continue;
}

if ($reflection->implementsInterface('PHP_CodeSniffer\Sniffs\Sniff') === false) {
// Skip classes which don't implement the register() or process() methods.
if (method_exists($className, 'register') === false
|| method_exists($className, 'process') === false
) {
$errorMsg = 'Sniff class %s is missing required method %s().';
if (method_exists($className, 'register') === false) {
$this->msgCache->add(sprintf($errorMsg, $className, 'register'), MessageCollector::ERROR);
}

if (method_exists($className, 'process') === false) {
$this->msgCache->add(sprintf($errorMsg, $className, 'process'), MessageCollector::ERROR);
}

continue;
}
}//end if

$listeners[$className] = $className;

if (PHP_CODESNIFFER_VERBOSITY > 2) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
/**
* Test fixture.
*
* @see \PHP_CodeSniffer\Tests\Core\Ruleset\RegisterSniffsRejectsInvalidSniffTest
*/

namespace Fixtures\TestStandard\Sniffs\InvalidSniffError;

final class NoImplementsNoProcessSniff
{

public function register()
{
return [T_OPEN_TAG];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
/**
* Test fixture.
*
* @see \PHP_CodeSniffer\Tests\Core\Ruleset\RegisterSniffsRejectsInvalidSniffTest
*/

namespace Fixtures\TestStandard\Sniffs\InvalidSniffError;

final class NoImplementsNoRegisterOrProcessSniff
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* Test fixture.
*
* @see \PHP_CodeSniffer\Tests\Core\Ruleset\RegisterSniffsRejectsInvalidSniffTest
*/

namespace Fixtures\TestStandard\Sniffs\InvalidSniffError;

use PHP_CodeSniffer\Files\File;

final class NoImplementsNoRegisterSniff
{

public function process(File $phpcsFile, $stackPtr)
{
// Do something.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<rule ref="TestStandard">
<exclude name="TestStandard.InvalidSniffs"/>
<exclude name="TestStandard.InvalidSniffError"/>
</rule>

</ruleset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RegisterSniffsRejectsInvalidSniffTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">

<rule ref="./tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffError/NoImplementsNoProcessSniff.php"/>

<!-- Prevent a "no sniff were registered" error. -->
<rule ref="Generic.PHP.BacktickOperator"/>
</ruleset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RegisterSniffsRejectsInvalidSniffTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">

<rule ref="./tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffError/NoImplementsNoRegisterOrProcessSniff.php"/>

<!-- Prevent a "no sniff were registered" error. -->
<rule ref="Generic.PHP.BacktickOperator"/>
</ruleset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="RegisterSniffsRejectsInvalidSniffTest" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">

<rule ref="./tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffError/NoImplementsNoRegisterSniff.php"/>

<!-- Prevent a "no sniff were registered" error. -->
<rule ref="Generic.PHP.BacktickOperator"/>
</ruleset>
80 changes: 80 additions & 0 deletions tests/Core/Ruleset/RegisterSniffsRejectsInvalidSniffTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php
/**
* Tests that invalid sniffs will be rejected with an informative error message.
*
* @author Juliette Reinders Folmer <[email protected]>
* @copyright 2025 PHPCSStandards and contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Ruleset;

use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHP_CodeSniffer\Tests\Core\Ruleset\AbstractRulesetTestCase;

/**
* Tests that invalid sniffs will be rejected with an informative error message.
*
* @covers \PHP_CodeSniffer\Ruleset::registerSniffs
*/
final class RegisterSniffsRejectsInvalidSniffTest extends AbstractRulesetTestCase
{


/**
* Verify that an error is thrown if an invalid sniff class is loaded.
*
* @param string $standard The standard to use for the test.
* @param string $methodName The name of the missing method.
*
* @dataProvider dataExceptionIsThrownOnMissingInterfaceMethod
*
* @return void
*/
public function testExceptionIsThrownOnMissingInterfaceMethod($standard, $methodName)
{
// Set up the ruleset.
$standard = __DIR__.'/'.$standard;
$config = new ConfigDouble(["--standard=$standard"]);

$regex = "`(^|\R)ERROR: Sniff class \S+Sniff is missing required method $methodName\(\)\.\R`";
$this->expectRuntimeExceptionRegex($regex);

new Ruleset($config);

}//end testExceptionIsThrownOnMissingInterfaceMethod()


/**
* Data provider.
*
* @see testExceptionIsThrownOnMissingInterfaceMethod()
*
* @return array<string, array<string, string>>
*/
public static function dataExceptionIsThrownOnMissingInterfaceMethod()
{
return [
'Missing register() method' => [
'standard' => 'RegisterSniffsRejectsInvalidSniffNoImplementsNoRegisterTest.xml',
'methodName' => 'register',
],
'Missing process() method' => [
'standard' => 'RegisterSniffsRejectsInvalidSniffNoImplementsNoProcessTest.xml',
'methodName' => 'process',
],
'Missing both, checking register() method' => [
'standard' => 'RegisterSniffsRejectsInvalidSniffNoImplementsNoRegisterOrProcessTest.xml',
'methodName' => 'register',
],
'Missing both, checking process() method' => [
'standard' => 'RegisterSniffsRejectsInvalidSniffNoImplementsNoRegisterOrProcessTest.xml',
'methodName' => 'process',
],
];

}//end dataExceptionIsThrownOnMissingInterfaceMethod()


}//end class
1 change: 1 addition & 0 deletions tests/Core/Ruleset/ShowSniffDeprecationsTest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<rule ref="TestStandard">
<exclude name="TestStandard.DeprecatedInvalid"/>
<exclude name="TestStandard.InvalidSniffs"/>
<exclude name="TestStandard.InvalidSniffError"/>
</rule>

</ruleset>