From 16b613dbdc0ed8d09867db977ca662ddb9a867e1 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Fri, 5 Jan 2024 09:20:06 +0100 Subject: [PATCH 1/3] PHPUnit config: make sure unexpected output fails the tests --- phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9fb6c304db..1402588f20 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,6 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.2/phpunit.xsd" backupGlobals="true" + beStrictAboutOutputDuringTests="true" beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="tests/bootstrap.php" convertErrorsToExceptions="true" From 2507c78d6aa8fe714b19d5e62519cd75f0a3aceb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Jan 2024 21:15:46 +0100 Subject: [PATCH 2/3] :sparkles: Native handling of sniff deprecations As per 164, this commit introduces a new feature to PHPCS: native handling of sniff deprecations. ### Background There are quite a few sniffs slated for removal in PHPCS 4.0 - 45 so far, to be exact - and save for two sniffs which have received a deprecation mention in the changelogs, this has only been announced in issues (squizlabs/PHP_CodeSniffer 2448 + squizlabs/PHP_CodeSniffer 2471) in the Squizlabs repo and should therefore only be considered "known" to a very small group of people. Increasing awareness of the upcoming sniff removals should allow for a smoother upgrade experience to PHPCS 4.0 for end-users. Aside from use by PHPCS itself, this feature can also be used by external standards to signal sniff deprecations to _their_ end-users. All in all, this feature should hopefully improve the end-user experience. ### Policy for use of this feature in PHP_CodeSniffer itself As per the notes in 188, the intention is for PHPCS itself to use this feature as follows: * Soft deprecate (changelog notice and `@deprecated` tag in sniff) sniffs during the lifetime of a major. * Hard deprecate sniffs, i.e. implement the `DeprecatedSniff` interface, in the last minor before the next major release. External standards are, of course, free to apply a different deprecation policy. ### Implementation details This commit introduces: #### A new `PHP_CodeSniffer\Sniffs\DeprecatedSniff` interface This interface enforces the implementation of three new methods: * `getDeprecationVersion(): string` - the return value should be a non-empty string with the version in which the sniff was deprecated. * `getRemovalVersion(): string` - the return value should be a non-empty string with the version in which the sniff will be removed. If the removal version is not yet known, it is recommended to set this to: "a future version". * `getDeprecationMessage(): string` - the return value allows for an arbitrary message to be added, such as a replacement recommendation. If no additional information needs to be conveyed to end-users, an empty string can be returned. Custom messages are allowed to contain new lines. #### Changes to the `Ruleset` class to allow for showing the deprecation notices The Ruleset class contains two new methods: * `hasSniffDeprecations(): bool` to allow for checking whether a loading ruleset includes deprecated sniffs. * `showSniffDeprecations(): void` to display the applicable deprecation notices. The implementation for showing the sniff deprecation notices is as follows: * No notices will be shown when PHPCS is running with the `-q` flag (quite mode). * No notices will be shown when PHPCS was giving the `-e` flag to "explain" a standard (list all sniffs). * No notices will be shown when PHPCS was asked to display documentation using the `--generator=...` CLI argument. * No notices will be shown when PHPCS is asked for information which doesn't involve loading a ruleset, such as running `phpcs` with the any of the following flags: `-i` (listing installed standards), `--version`, `--help`, `--config-show`, `--config-set`, `--config-delete`. * Only deprecation notices will be shown for _active_ sniffs. This means that when `--exclude=...` is used and deprecated sniffs are excluded, no notices will be shown for the excluded sniffs. It also means that when `--sniffs=...` is used, deprecation notices will only be shown for those sniffs selected (if deprecated). * The deprecation notices have no impact on the PHPCS (or PHPCBF) run itself. - Deprecated sniffs will still be loaded and run. - Properties can be set on deprecated sniffs. - The exit codes of runs are not affected by whether or not a ruleset contains deprecated sniffs. * As things are, deprecation notices will show both for `phpcs` as well as `phpcbf` runs. I did considered silencing them by default for `phpcbf`. For now, however, I've decided against this as the whole point of showing deprecation notices is to increase awareness of upcoming changes and as a subsection of the PHPCS users only run `phpcbf` and rarely `phpcs`, silencing the notices for `phpcbf` could be counter-productive. Additional implementation notes: * Any user set `--report-width` will be respected. This includes when the `report-width` is set to `auto`. * New lines in custom messages will be normalized to be suitable for the OS of the end-user. * The output will have select colourization if `--colors` is turned on. * The deprecation notices will not be included in generated reports saved to file using `--report-file=...` (and variants thereof). * If the interface is implemented incorrectly, a `PHP_CodeSniffer\Exceptions\RuntimeException` will be thrown. The intention is to add return type declarations to the interface in the future. The complete new feature is extensively covered by tests. Related issues: 188, 276 Fixes 164 --- src/Ruleset.php | 155 ++++++ src/Runner.php | 4 + src/Sniffs/DeprecatedSniff.php | 63 +++ .../Deprecated/WithLongReplacementSniff.php | 41 ++ ...eplacementContainingLinuxNewlinesSniff.php | 45 ++ ...WithReplacementContainingNewlinesSniff.php | 45 ++ .../Deprecated/WithReplacementSniff.php | 41 ++ .../Deprecated/WithoutReplacementSniff.php | 41 ++ .../EmptyDeprecationVersionSniff.php | 41 ++ .../EmptyRemovalVersionSniff.php | 41 ++ .../InvalidDeprecationMessageSniff.php | 42 ++ .../InvalidDeprecationVersionSniff.php | 41 ++ .../InvalidRemovalVersionSniff.php | 41 ++ tests/Core/Ruleset/Fixtures/ruleset.xml | 4 + ...eprecationsEmptyDeprecationVersionTest.xml | 8 + ...iffDeprecationsEmptyRemovalVersionTest.xml | 8 + ...recationsInvalidDeprecationMessageTest.xml | 8 + ...recationsInvalidDeprecationVersionTest.xml | 8 + ...fDeprecationsInvalidRemovalVersionTest.xml | 8 + .../ShowSniffDeprecationsOrderTest.xml | 10 + .../ShowSniffDeprecationsReportWidthTest.xml | 8 + .../Ruleset/ShowSniffDeprecationsTest.php | 510 ++++++++++++++++++ .../Ruleset/ShowSniffDeprecationsTest.xml | 10 + 23 files changed, 1223 insertions(+) create mode 100644 src/Sniffs/DeprecatedSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithLongReplacementSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithReplacementContainingLinuxNewlinesSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithReplacementContainingNewlinesSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithReplacementSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithoutReplacementSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/DeprecatedInvalid/EmptyDeprecationVersionSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/DeprecatedInvalid/EmptyRemovalVersionSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/DeprecatedInvalid/InvalidDeprecationMessageSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/DeprecatedInvalid/InvalidDeprecationVersionSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/Sniffs/DeprecatedInvalid/InvalidRemovalVersionSniff.php create mode 100644 tests/Core/Ruleset/Fixtures/ruleset.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsEmptyDeprecationVersionTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsEmptyRemovalVersionTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationMessageTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationVersionTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsInvalidRemovalVersionTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsOrderTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsReportWidthTest.xml create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsTest.php create mode 100644 tests/Core/Ruleset/ShowSniffDeprecationsTest.xml diff --git a/src/Ruleset.php b/src/Ruleset.php index 2e2493037b..7c7b1a4b3c 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -12,6 +12,7 @@ namespace PHP_CodeSniffer; use PHP_CodeSniffer\Exceptions\RuntimeException; +use PHP_CodeSniffer\Sniffs\DeprecatedSniff; use PHP_CodeSniffer\Util; use stdClass; @@ -116,6 +117,16 @@ class Ruleset */ private $config = null; + /** + * An array of the names of sniffs which have been marked as deprecated. + * + * The key is the sniff code and the value + * is the fully qualified name of the sniff class. + * + * @var array + */ + private $deprecatedSniffs = []; + /** * Initialise the ruleset that the run will use. @@ -297,6 +308,146 @@ public function explain() }//end explain() + /** + * Checks whether any deprecated sniffs were registered via the ruleset. + * + * @return bool + */ + public function hasSniffDeprecations() + { + return (count($this->deprecatedSniffs) > 0); + + }//end hasSniffDeprecations() + + + /** + * Prints an information block about deprecated sniffs being used. + * + * @return void + * + * @throws \PHP_CodeSniffer\Exceptions\RuntimeException When the interface implementation is faulty. + */ + public function showSniffDeprecations() + { + if ($this->hasSniffDeprecations() === false) { + return; + } + + // Don't show deprecation notices in quiet mode, in explain mode + // or when the documentation is being shown. + // Documentation and explain will mark a sniff as deprecated natively + // and also call the Ruleset multiple times which would lead to duplicate + // display of the deprecation messages. + if ($this->config->quiet === true + || $this->config->explain === true + || $this->config->generator !== null + ) { + return; + } + + $reportWidth = $this->config->reportWidth; + // Message takes report width minus the leading dash + two spaces, minus a one space gutter at the end. + $maxMessageWidth = ($reportWidth - 4); + $maxActualWidth = 0; + + ksort($this->deprecatedSniffs, (SORT_NATURAL | SORT_FLAG_CASE)); + + $messages = []; + $messageTemplate = 'This sniff has been deprecated since %s and will be removed in %s. %s'; + $errorTemplate = 'The %s::%s() method must return a %sstring, received %s'; + + foreach ($this->deprecatedSniffs as $sniffCode => $className) { + if (isset($this->sniffs[$className]) === false) { + // Should only be possible in test situations, but some extra defensive coding is never a bad thing. + continue; + } + + // Verify the interface was implemented correctly. + // Unfortunately can't be safeguarded via type declarations yet. + $deprecatedSince = $this->sniffs[$className]->getDeprecationVersion(); + if (is_string($deprecatedSince) === false) { + throw new RuntimeException( + sprintf($errorTemplate, $className, 'getDeprecationVersion', 'non-empty ', gettype($deprecatedSince)) + ); + } + + if ($deprecatedSince === '') { + throw new RuntimeException( + sprintf($errorTemplate, $className, 'getDeprecationVersion', 'non-empty ', '""') + ); + } + + $removedIn = $this->sniffs[$className]->getRemovalVersion(); + if (is_string($removedIn) === false) { + throw new RuntimeException( + sprintf($errorTemplate, $className, 'getRemovalVersion', 'non-empty ', gettype($removedIn)) + ); + } + + if ($removedIn === '') { + throw new RuntimeException( + sprintf($errorTemplate, $className, 'getRemovalVersion', 'non-empty ', '""') + ); + } + + $customMessage = $this->sniffs[$className]->getDeprecationMessage(); + if (is_string($customMessage) === false) { + throw new RuntimeException( + sprintf($errorTemplate, $className, 'getDeprecationMessage', '', gettype($customMessage)) + ); + } + + // Truncate the error code if there is not enough report width. + if (strlen($sniffCode) > $maxMessageWidth) { + $sniffCode = substr($sniffCode, 0, ($maxMessageWidth - 3)).'...'; + } + + $message = '- '.$sniffCode.PHP_EOL; + if ($this->config->colors === true) { + $message = '- '."\033[36m".$sniffCode."\033[0m".PHP_EOL; + } + + $maxActualWidth = max($maxActualWidth, strlen($sniffCode)); + + // Normalize new line characters in custom message. + $customMessage = preg_replace('`\R`', PHP_EOL, $customMessage); + + $notice = trim(sprintf($messageTemplate, $deprecatedSince, $removedIn, $customMessage)); + $maxActualWidth = max($maxActualWidth, min(strlen($notice), $maxMessageWidth)); + $wrapped = wordwrap($notice, $maxMessageWidth, PHP_EOL); + $message .= ' '.implode(PHP_EOL.' ', explode(PHP_EOL, $wrapped)); + + $messages[] = $message; + }//end foreach + + if (count($messages) === 0) { + return; + } + + $summaryLine = "WARNING: The $this->name standard uses 1 deprecated sniff"; + $sniffCount = count($messages); + if ($sniffCount !== 1) { + $summaryLine = str_replace('1 deprecated sniff', "$sniffCount deprecated sniffs", $summaryLine); + } + + $maxActualWidth = max($maxActualWidth, min(strlen($summaryLine), $maxMessageWidth)); + + $summaryLine = wordwrap($summaryLine, $reportWidth, PHP_EOL); + if ($this->config->colors === true) { + echo "\033[33m".$summaryLine."\033[0m".PHP_EOL; + } else { + echo $summaryLine.PHP_EOL; + } + + echo str_repeat('-', min(($maxActualWidth + 4), $reportWidth)).PHP_EOL; + echo implode(PHP_EOL, $messages); + + $closer = wordwrap('Deprecated sniffs are still run, but will stop working at some point in the future.', $reportWidth, PHP_EOL); + echo PHP_EOL.PHP_EOL.$closer.PHP_EOL.PHP_EOL; + + }//end showSniffDeprecations() + + /** * Processes a single ruleset and returns a list of the sniffs it represents. * @@ -1225,6 +1376,10 @@ public function populateTokenListeners() $sniffCode = Util\Common::getSniffCode($sniffClass); $this->sniffCodes[$sniffCode] = $sniffClass; + if ($this->sniffs[$sniffClass] instanceof DeprecatedSniff) { + $this->deprecatedSniffs[$sniffCode] = $sniffClass; + } + // Set custom properties. if (isset($this->ruleset[$sniffCode]['properties']) === true) { foreach ($this->ruleset[$sniffCode]['properties'] as $name => $settings) { diff --git a/src/Runner.php b/src/Runner.php index 61013874c9..053c67cfaf 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -334,6 +334,10 @@ public function init() // should be checked and/or fixed. try { $this->ruleset = new Ruleset($this->config); + + if ($this->ruleset->hasSniffDeprecations() === true) { + $this->ruleset->showSniffDeprecations(); + } } catch (RuntimeException $e) { $error = 'ERROR: '.$e->getMessage().PHP_EOL.PHP_EOL; $error .= $this->config->printShortUsage(true); diff --git a/src/Sniffs/DeprecatedSniff.php b/src/Sniffs/DeprecatedSniff.php new file mode 100644 index 0000000000..4f4d9fc025 --- /dev/null +++ b/src/Sniffs/DeprecatedSniff.php @@ -0,0 +1,63 @@ + + * @copyright 2024 PHPCSStandards Contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Sniffs; + +interface DeprecatedSniff +{ + + + /** + * Provide the version number in which the sniff was deprecated. + * + * Recommended format for PHPCS native sniffs: "v3.3.0". + * Recommended format for external sniffs: "StandardName v3.3.0". + * + * @return string + */ + public function getDeprecationVersion(); + + + /** + * Provide the version number in which the sniff will be removed. + * + * Recommended format for PHPCS native sniffs: "v3.3.0". + * Recommended format for external sniffs: "StandardName v3.3.0". + * + * If the removal version is not yet known, it is recommended to set + * this to: "a future version". + * + * @return string + */ + public function getRemovalVersion(); + + + /** + * Optionally provide an arbitrary custom message to display with the deprecation. + * + * Typically intended to allow for displaying information about what to + * replace the deprecated sniff with. + * Example: "Use the Stnd.Cat.SniffName sniff instead." + * Multi-line messages (containing new line characters) are supported. + * + * An empty string can be returned if there is no replacement/no need + * for a custom message. + * + * @return string + */ + public function getDeprecationMessage(); + + +}//end interface diff --git a/tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithLongReplacementSniff.php b/tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithLongReplacementSniff.php new file mode 100644 index 0000000000..40c23113d5 --- /dev/null +++ b/tests/Core/Ruleset/Fixtures/Sniffs/Deprecated/WithLongReplacementSniff.php @@ -0,0 +1,41 @@ + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsEmptyDeprecationVersionTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsEmptyDeprecationVersionTest.xml new file mode 100644 index 0000000000..5e2480bf2f --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsEmptyDeprecationVersionTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsEmptyRemovalVersionTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsEmptyRemovalVersionTest.xml new file mode 100644 index 0000000000..6e667375af --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsEmptyRemovalVersionTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationMessageTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationMessageTest.xml new file mode 100644 index 0000000000..d325167994 --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationMessageTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationVersionTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationVersionTest.xml new file mode 100644 index 0000000000..89d83ab528 --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidDeprecationVersionTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsInvalidRemovalVersionTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidRemovalVersionTest.xml new file mode 100644 index 0000000000..c1eb062e1c --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsInvalidRemovalVersionTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsOrderTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsOrderTest.xml new file mode 100644 index 0000000000..3ce96ce89f --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsOrderTest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsReportWidthTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsReportWidthTest.xml new file mode 100644 index 0000000000..9f1cc85155 --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsReportWidthTest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsTest.php b/tests/Core/Ruleset/ShowSniffDeprecationsTest.php new file mode 100644 index 0000000000..8e81f96a25 --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsTest.php @@ -0,0 +1,510 @@ + + * @copyright 2024 Juliette Reinders Folmer. All rights reserved. + * @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 PHPUnit\Framework\TestCase; + +/** + * Tests PHPCS native handling of sniff deprecations. + * + * @covers \PHP_CodeSniffer\Ruleset::hasSniffDeprecations + * @covers \PHP_CodeSniffer\Ruleset::showSniffDeprecations + */ +final class ShowSniffDeprecationsTest extends TestCase +{ + + + /** + * Test the return value of the hasSniffDeprecations() method. + * + * @param string $standard The standard to use for the test. + * @param bool $expected The expected function return value. + * + * @dataProvider dataHasSniffDeprecations + * + * @return void + */ + public function testHasSniffDeprecations($standard, $expected) + { + $config = new ConfigDouble(['.', "--standard=$standard"]); + $ruleset = new Ruleset($config); + + $this->assertSame($expected, $ruleset->hasSniffDeprecations()); + + }//end testHasSniffDeprecations() + + + /** + * Data provider. + * + * @see testHasSniffDeprecations() + * + * @return array> + */ + public static function dataHasSniffDeprecations() + { + return [ + 'Standard not using deprecated sniffs: PSR1' => [ + 'standard' => 'PSR1', + 'expected' => false, + ], + 'Standard using deprecated sniffs: Test Fixture' => [ + 'standard' => __DIR__.'/ShowSniffDeprecationsTest.xml', + 'expected' => true, + ], + ]; + + }//end dataHasSniffDeprecations() + + + /** + * Test that the listing with deprecated sniffs will not show when specific command-line options are being used. + * + * @param string $standard The standard to use for the test. + * @param array $additionalArgs Optional. Additional arguments to pass. + * + * @dataProvider dataDeprecatedSniffsListDoesNotShow + * + * @return void + */ + public function testDeprecatedSniffsListDoesNotShow($standard, $additionalArgs=[]) + { + $args = $additionalArgs; + $args[] = '.'; + $args[] = "--standard=$standard"; + + $config = new ConfigDouble($args); + $ruleset = new Ruleset($config); + + $this->expectOutputString(''); + + $ruleset->showSniffDeprecations(); + + }//end testDeprecatedSniffsListDoesNotShow() + + + /** + * Data provider. + * + * @see testDeprecatedSniffsListDoesNotShow() + * + * @return array>> + */ + public static function dataDeprecatedSniffsListDoesNotShow() + { + return [ + 'Standard not using deprecated sniffs: PSR1' => [ + 'standard' => 'PSR1', + ], + 'Standard using deprecated sniffs; explain mode' => [ + 'standard' => __DIR__.'/ShowSniffDeprecationsTest.xml', + 'additionalArgs' => ['-e'], + ], + 'Standard using deprecated sniffs; quiet mode' => [ + 'standard' => __DIR__.'/ShowSniffDeprecationsTest.xml', + 'additionalArgs' => ['-q'], + ], + 'Standard using deprecated sniffs; documentation is requested' => [ + 'standard' => __DIR__.'/ShowSniffDeprecationsTest.xml', + 'additionalArgs' => ['--generator=text'], + ], + ]; + + }//end dataDeprecatedSniffsListDoesNotShow() + + + /** + * Test that the listing with deprecated sniffs will not show when using a standard containing deprecated sniffs, + * but only running select non-deprecated sniffs (using `--sniffs=...`). + * + * @return void + */ + public function testDeprecatedSniffsListDoesNotShowWhenSelectedSniffsAreNotDeprecated() + { + $standard = __DIR__.'/ShowSniffDeprecationsTest.xml'; + $config = new ConfigDouble(['.', "--standard=$standard"]); + $ruleset = new Ruleset($config); + + /* + * Apply sniff restrictions. + * For tests we need to manually trigger this if the standard is "installed", like with the fixtures these tests use. + */ + + $restrictions = []; + $sniffs = [ + 'Fixtures.SetProperty.AllowedAsDeclared', + 'Fixtures.SetProperty.AllowedViaStdClass', + ]; + foreach ($sniffs as $sniffCode) { + $parts = explode('.', strtolower($sniffCode)); + $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; + $restrictions[strtolower($sniffName)] = true; + } + + $sniffFiles = []; + $allSniffs = $ruleset->sniffCodes; + foreach ($allSniffs as $sniffName) { + $sniffFile = str_replace('\\', DIRECTORY_SEPARATOR, $sniffName); + $sniffFile = __DIR__.DIRECTORY_SEPARATOR.$sniffFile.'.php'; + $sniffFiles[] = $sniffFile; + } + + $ruleset->registerSniffs($allSniffs, $restrictions, []); + $ruleset->populateTokenListeners(); + + $this->expectOutputString(''); + + $ruleset->showSniffDeprecations(); + + }//end testDeprecatedSniffsListDoesNotShowWhenSelectedSniffsAreNotDeprecated() + + + /** + * Test that the listing with deprecated sniffs will not show when using a standard containing deprecated sniffs, + * but all deprecated sniffs have been excluded from the run (using `--exclude=...`). + * + * @return void + */ + public function testDeprecatedSniffsListDoesNotShowWhenAllDeprecatedSniffsAreExcluded() + { + $standard = __DIR__.'/ShowSniffDeprecationsTest.xml'; + $config = new ConfigDouble(['.', "--standard=$standard"]); + $ruleset = new Ruleset($config); + + /* + * Apply sniff restrictions. + * For tests we need to manually trigger this if the standard is "installed", like with the fixtures these tests use. + */ + + $exclusions = []; + $exclude = [ + 'Fixtures.Deprecated.WithLongReplacement', + 'Fixtures.Deprecated.WithoutReplacement', + 'Fixtures.Deprecated.WithReplacement', + 'Fixtures.Deprecated.WithReplacementContainingLinuxNewlines', + 'Fixtures.Deprecated.WithReplacementContainingNewlines', + ]; + foreach ($exclude as $sniffCode) { + $parts = explode('.', strtolower($sniffCode)); + $sniffName = $parts[0].'\sniffs\\'.$parts[1].'\\'.$parts[2].'sniff'; + $exclusions[strtolower($sniffName)] = true; + } + + $sniffFiles = []; + $allSniffs = $ruleset->sniffCodes; + foreach ($allSniffs as $sniffName) { + $sniffFile = str_replace('\\', DIRECTORY_SEPARATOR, $sniffName); + $sniffFile = __DIR__.DIRECTORY_SEPARATOR.$sniffFile.'.php'; + $sniffFiles[] = $sniffFile; + } + + $ruleset->registerSniffs($allSniffs, [], $exclusions); + $ruleset->populateTokenListeners(); + + $this->expectOutputString(''); + + $ruleset->showSniffDeprecations(); + + }//end testDeprecatedSniffsListDoesNotShowWhenAllDeprecatedSniffsAreExcluded() + + + /** + * Test deprecated sniffs are listed alphabetically in the deprecated sniffs warning. + * + * This tests a number of different aspects: + * 1. That the summary line uses the correct grammar when there is are multiple deprecated sniffs. + * 2. That there is no trailing whitespace when the sniff does not provide a custom message. + * 3. That custom messages containing new line characters (any type) are handled correctly and + * that those new line characters are converted to the OS supported new line char. + * + * @return void + */ + public function testDeprecatedSniffsWarning() + { + $standard = __DIR__.'/ShowSniffDeprecationsTest.xml'; + $config = new ConfigDouble(["--standard=$standard", '--no-colors']); + $ruleset = new Ruleset($config); + + $expected = 'WARNING: The SniffDeprecationTest standard uses 5 deprecated sniffs'.PHP_EOL; + $expected .= '--------------------------------------------------------------------------------'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithLongReplacement'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= ' Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vel'.PHP_EOL; + $expected .= ' vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium sed.'.PHP_EOL; + $expected .= ' Fusce egestas congue massa semper cursus. Donec quis pretium tellus. In'.PHP_EOL; + $expected .= ' lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan'.PHP_EOL; + $expected .= ' eros sapien at sem. Sed pulvinar aliquam malesuada. Aliquam erat volutpat.'.PHP_EOL; + $expected .= ' Mauris gravida rutrum lectus at egestas. Fusce tempus elit in tincidunt'.PHP_EOL; + $expected .= ' dictum. Suspendisse dictum egestas sapien, eget ullamcorper metus elementum'.PHP_EOL; + $expected .= ' semper. Vestibulum sem justo, consectetur ac tincidunt et, finibus eget'.PHP_EOL; + $expected .= ' libero.'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithoutReplacement'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.4.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithReplacement'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= ' Use the Stnd.Category.OtherSniff sniff instead.'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithReplacementContainingLinuxNewlines'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= ' Lorem ipsum dolor sit amet, consectetur adipiscing elit.'.PHP_EOL; + $expected .= ' Fusce vel vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium'.PHP_EOL; + $expected .= ' sed.'.PHP_EOL; + $expected .= ' Fusce egestas congue massa semper cursus. Donec quis pretium tellus.'.PHP_EOL; + $expected .= ' In lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan'.PHP_EOL; + $expected .= ' eros sapien at sem.'.PHP_EOL; + $expected .= ' Sed pulvinar aliquam malesuada. Aliquam erat volutpat. Mauris gravida rutrum'.PHP_EOL; + $expected .= ' lectus at egestas.'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithReplacementContainingNewlines'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= ' Lorem ipsum dolor sit amet, consectetur adipiscing elit.'.PHP_EOL; + $expected .= ' Fusce vel vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium'.PHP_EOL; + $expected .= ' sed.'.PHP_EOL; + $expected .= ' Fusce egestas congue massa semper cursus. Donec quis pretium tellus.'.PHP_EOL; + $expected .= ' In lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan'.PHP_EOL; + $expected .= ' eros sapien at sem.'.PHP_EOL; + $expected .= ' Sed pulvinar aliquam malesuada. Aliquam erat volutpat. Mauris gravida rutrum'.PHP_EOL; + $expected .= ' lectus at egestas'.PHP_EOL.PHP_EOL; + $expected .= 'Deprecated sniffs are still run, but will stop working at some point in the'.PHP_EOL; + $expected .= 'future.'.PHP_EOL.PHP_EOL; + + $this->expectOutputString($expected); + + $ruleset->showSniffDeprecations(); + + }//end testDeprecatedSniffsWarning() + + + /** + * Test deprecated sniffs are listed alphabetically in the deprecated sniffs warning. + * + * This tests the following aspects: + * 1. That the summary line uses the correct grammar when there is a single deprecated sniff. + * 2. That the separator line below the summary maximizes at the longest line length. + * 3. That the word wrapping respects the maximum report width. + * 4. That the sniff name is truncated if it is longer than the max report width. + * + * @param int $reportWidth Report width for the test. + * @param string $expectedOutput Expected output. + * + * @dataProvider dataReportWidthIsRespected + * + * @return void + */ + public function testReportWidthIsRespected($reportWidth, $expectedOutput) + { + // Set up the ruleset. + $standard = __DIR__.'/ShowSniffDeprecationsReportWidthTest.xml'; + $config = new ConfigDouble(['.', "--standard=$standard", "--report-width=$reportWidth", '--no-colors']); + $ruleset = new Ruleset($config); + + $this->expectOutputString($expectedOutput); + + $ruleset->showSniffDeprecations(); + + }//end testReportWidthIsRespected() + + + /** + * Data provider. + * + * @see testReportWidthIsRespected() + * + * @return array> + */ + public static function dataReportWidthIsRespected() + { + $summaryLine = 'WARNING: The SniffDeprecationTest standard uses 1 deprecated sniff'.PHP_EOL; + + // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- Test readability is more important. + return [ + 'Report width small: 40; with truncated sniff name and wrapped header and footer lines' => [ + 'reportWidth' => 40, + 'expectedOutput' => 'WARNING: The SniffDeprecationTest'.PHP_EOL + .'standard uses 1 deprecated sniff'.PHP_EOL + .'----------------------------------------'.PHP_EOL + .'- Fixtures.Deprecated.WithLongRepla...'.PHP_EOL + .' This sniff has been deprecated since'.PHP_EOL + .' v3.8.0 and will be removed in'.PHP_EOL + .' v4.0.0. Lorem ipsum dolor sit amet,'.PHP_EOL + .' consectetur adipiscing elit. Fusce'.PHP_EOL + .' vel vestibulum nunc. Sed luctus'.PHP_EOL + .' dolor tortor, eu euismod purus'.PHP_EOL + .' pretium sed. Fusce egestas congue'.PHP_EOL + .' massa semper cursus. Donec quis'.PHP_EOL + .' pretium tellus. In lacinia, augue ut'.PHP_EOL + .' ornare porttitor, diam nunc faucibus'.PHP_EOL + .' purus, et accumsan eros sapien at'.PHP_EOL + .' sem. Sed pulvinar aliquam malesuada.'.PHP_EOL + .' Aliquam erat volutpat. Mauris'.PHP_EOL + .' gravida rutrum lectus at egestas.'.PHP_EOL + .' Fusce tempus elit in tincidunt'.PHP_EOL + .' dictum. Suspendisse dictum egestas'.PHP_EOL + .' sapien, eget ullamcorper metus'.PHP_EOL + .' elementum semper. Vestibulum sem'.PHP_EOL + .' justo, consectetur ac tincidunt et,'.PHP_EOL + .' finibus eget libero.'.PHP_EOL.PHP_EOL + .'Deprecated sniffs are still run, but'.PHP_EOL + .'will stop working at some point in the'.PHP_EOL + .'future.'.PHP_EOL.PHP_EOL, + ], + 'Report width default: 80' => [ + 'reportWidth' => 80, + 'expectedOutput' => $summaryLine.str_repeat('-', 80).PHP_EOL + .'- Fixtures.Deprecated.WithLongReplacement'.PHP_EOL + .' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL + .' Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vel'.PHP_EOL + .' vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium sed.'.PHP_EOL + .' Fusce egestas congue massa semper cursus. Donec quis pretium tellus. In'.PHP_EOL + .' lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan'.PHP_EOL + .' eros sapien at sem. Sed pulvinar aliquam malesuada. Aliquam erat volutpat.'.PHP_EOL + .' Mauris gravida rutrum lectus at egestas. Fusce tempus elit in tincidunt'.PHP_EOL + .' dictum. Suspendisse dictum egestas sapien, eget ullamcorper metus elementum'.PHP_EOL + .' semper. Vestibulum sem justo, consectetur ac tincidunt et, finibus eget'.PHP_EOL + .' libero.'.PHP_EOL.PHP_EOL + .'Deprecated sniffs are still run, but will stop working at some point in the'.PHP_EOL + .'future.'.PHP_EOL.PHP_EOL, + ], + 'Report width matches longest line: 666; the message should not wrap' => [ + // Length = 4 padding + 75 base line + 587 custom message. + 'reportWidth' => 666, + 'expectedOutput' => $summaryLine.str_repeat('-', 666).PHP_EOL + .'- Fixtures.Deprecated.WithLongReplacement'.PHP_EOL + .' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vel vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium sed. Fusce egestas congue massa semper cursus. Donec quis pretium tellus. In lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan eros sapien at sem. Sed pulvinar aliquam malesuada. Aliquam erat volutpat. Mauris gravida rutrum lectus at egestas. Fusce tempus elit in tincidunt dictum. Suspendisse dictum egestas sapien, eget ullamcorper metus elementum semper. Vestibulum sem justo, consectetur ac tincidunt et, finibus eget libero.' + .PHP_EOL.PHP_EOL + .'Deprecated sniffs are still run, but will stop working at some point in the future.'.PHP_EOL.PHP_EOL, + ], + 'Report width wide: 1000; delimiter line length should match longest line' => [ + 'reportWidth' => 1000, + 'expectedOutput' => $summaryLine.str_repeat('-', 666).PHP_EOL + .'- Fixtures.Deprecated.WithLongReplacement'.PHP_EOL + .' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vel vestibulum nunc. Sed luctus dolor tortor, eu euismod purus pretium sed. Fusce egestas congue massa semper cursus. Donec quis pretium tellus. In lacinia, augue ut ornare porttitor, diam nunc faucibus purus, et accumsan eros sapien at sem. Sed pulvinar aliquam malesuada. Aliquam erat volutpat. Mauris gravida rutrum lectus at egestas. Fusce tempus elit in tincidunt dictum. Suspendisse dictum egestas sapien, eget ullamcorper metus elementum semper. Vestibulum sem justo, consectetur ac tincidunt et, finibus eget libero.' + .PHP_EOL.PHP_EOL + .'Deprecated sniffs are still run, but will stop working at some point in the future.'.PHP_EOL.PHP_EOL, + ], + ]; + // phpcs:enable + + }//end dataReportWidthIsRespected() + + + /** + * Test deprecated sniffs are listed alphabetically in the deprecated sniffs warning. + * + * Additionally, this test verifies that deprecated sniffs are still registered to run. + * + * @return void + */ + public function testDeprecatedSniffsAreListedAlphabetically() + { + // Set up the ruleset. + $standard = __DIR__.'/ShowSniffDeprecationsOrderTest.xml'; + $config = new ConfigDouble(["--standard=$standard", '--no-colors']); + $ruleset = new Ruleset($config); + + $expected = 'WARNING: The SniffDeprecationTest standard uses 2 deprecated sniffs'.PHP_EOL; + $expected .= '--------------------------------------------------------------------------------'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithoutReplacement'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.4.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= '- Fixtures.Deprecated.WithReplacement'.PHP_EOL; + $expected .= ' This sniff has been deprecated since v3.8.0 and will be removed in v4.0.0.'.PHP_EOL; + $expected .= ' Use the Stnd.Category.OtherSniff sniff instead.'.PHP_EOL.PHP_EOL; + $expected .= 'Deprecated sniffs are still run, but will stop working at some point in the'.PHP_EOL; + $expected .= 'future.'.PHP_EOL.PHP_EOL; + + $this->expectOutputString($expected); + + $ruleset->showSniffDeprecations(); + + // Verify that the sniffs have been registered to run. + $this->assertCount(2, $ruleset->sniffCodes, 'Incorrect number of sniff codes registered'); + $this->assertArrayHasKey( + 'Fixtures.Deprecated.WithoutReplacement', + $ruleset->sniffCodes, + 'WithoutReplacement sniff not registered' + ); + $this->assertArrayHasKey( + 'Fixtures.Deprecated.WithReplacement', + $ruleset->sniffCodes, + 'WithReplacement sniff not registered' + ); + + }//end testDeprecatedSniffsAreListedAlphabetically() + + + /** + * Test that an exception is thrown when any of the interface required methods does not + * comply with the return type/value requirements. + * + * @param string $standard The standard to use for the test. + * @param string $exceptionMessage The contents of the expected exception message. + * + * @dataProvider dataExceptionIsThrownOnIncorrectlyImplementedInterface + * + * @return void + */ + public function testExceptionIsThrownOnIncorrectlyImplementedInterface($standard, $exceptionMessage) + { + $exception = 'PHP_CodeSniffer\Exceptions\RuntimeException'; + if (method_exists($this, 'expectException') === true) { + // PHPUnit 5+. + $this->expectException($exception); + $this->expectExceptionMessage($exceptionMessage); + } else { + // PHPUnit 4. + $this->setExpectedException($exception, $exceptionMessage); + } + + // Set up the ruleset. + $standard = __DIR__.'/'.$standard; + $config = new ConfigDouble(["--standard=$standard"]); + $ruleset = new Ruleset($config); + + $ruleset->showSniffDeprecations(); + + }//end testExceptionIsThrownOnIncorrectlyImplementedInterface() + + + /** + * Data provider. + * + * @see testExceptionIsThrownOnIncorrectlyImplementedInterface() + * + * @return array> + */ + public static function dataExceptionIsThrownOnIncorrectlyImplementedInterface() + { + return [ + 'getDeprecationVersion() does not return a string' => [ + 'standard' => 'ShowSniffDeprecationsInvalidDeprecationVersionTest.xml', + 'exceptionMessage' => 'The Fixtures\Sniffs\DeprecatedInvalid\InvalidDeprecationVersionSniff::getDeprecationVersion() method must return a non-empty string, received double', + ], + 'getRemovalVersion() does not return a string' => [ + 'standard' => 'ShowSniffDeprecationsInvalidRemovalVersionTest.xml', + 'exceptionMessage' => 'The Fixtures\Sniffs\DeprecatedInvalid\InvalidRemovalVersionSniff::getRemovalVersion() method must return a non-empty string, received array', + ], + 'getDeprecationMessage() does not return a string' => [ + 'standard' => 'ShowSniffDeprecationsInvalidDeprecationMessageTest.xml', + 'exceptionMessage' => 'The Fixtures\Sniffs\DeprecatedInvalid\InvalidDeprecationMessageSniff::getDeprecationMessage() method must return a string, received object', + ], + 'getDeprecationVersion() returns an empty string' => [ + 'standard' => 'ShowSniffDeprecationsEmptyDeprecationVersionTest.xml', + 'exceptionMessage' => 'The Fixtures\Sniffs\DeprecatedInvalid\EmptyDeprecationVersionSniff::getDeprecationVersion() method must return a non-empty string, received ""', + ], + 'getRemovalVersion() returns an empty string' => [ + 'standard' => 'ShowSniffDeprecationsEmptyRemovalVersionTest.xml', + 'exceptionMessage' => 'The Fixtures\Sniffs\DeprecatedInvalid\EmptyRemovalVersionSniff::getRemovalVersion() method must return a non-empty string, received ""', + ], + ]; + + }//end dataExceptionIsThrownOnIncorrectlyImplementedInterface() + + +}//end class diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml new file mode 100644 index 0000000000..4c1dec2203 --- /dev/null +++ b/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml @@ -0,0 +1,10 @@ + + + + + + + + + + From 3fb0d958b05c53443be907ebf42a190577c90abb Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 16 Jan 2024 21:18:31 +0100 Subject: [PATCH 3/3] Explain: mark deprecated sniffs as such This commit makes a small adjustment to the output of the `-e` (explain) command. Deprecated sniffs will now be marked with a trailing `*` asterix and if the standard contains deprecated sniffs, a line will show at the end of the output to explain that the `*` means that a sniff is deprecated. Includes a test documenting and safeguarding this behaviour. --- src/Ruleset.php | 8 ++++++ tests/Core/Ruleset/ExplainTest.php | 42 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/Ruleset.php b/src/Ruleset.php index 7c7b1a4b3c..b99d484fef 100644 --- a/src/Ruleset.php +++ b/src/Ruleset.php @@ -301,10 +301,18 @@ public function explain() } }//end if + if (isset($this->deprecatedSniffs[$sniff]) === true) { + $sniff .= ' *'; + } + $sniffsInStandard[] = $sniff; ++$lastCount; }//end foreach + if (count($this->deprecatedSniffs) > 0) { + echo PHP_EOL.'* Sniffs marked with an asterix are deprecated.'.PHP_EOL; + } + }//end explain() diff --git a/tests/Core/Ruleset/ExplainTest.php b/tests/Core/Ruleset/ExplainTest.php index 73943d3757..fc84f88ac7 100644 --- a/tests/Core/Ruleset/ExplainTest.php +++ b/tests/Core/Ruleset/ExplainTest.php @@ -166,6 +166,48 @@ public function testExplainCustomRuleset() }//end testExplainCustomRuleset() + /** + * Test the output of the "explain" command for a standard containing both deprecated + * and non-deprecated sniffs. + * + * Tests that: + * - Deprecated sniffs are marked with an asterix in the list. + * - A footnote is displayed explaining the asterix. + * - And that the "standard uses # deprecated sniffs" listing is **not** displayed. + * + * @return void + */ + public function testExplainWithDeprecatedSniffs() + { + // Set up the ruleset. + $standard = __DIR__."/ShowSniffDeprecationsTest.xml"; + $config = new ConfigDouble(["--standard=$standard", '-e']); + $ruleset = new Ruleset($config); + + $expected = PHP_EOL; + $expected .= 'The SniffDeprecationTest standard contains 9 sniffs'.PHP_EOL.PHP_EOL; + + $expected .= 'Fixtures (9 sniffs)'.PHP_EOL; + $expected .= '-------------------'.PHP_EOL; + $expected .= ' Fixtures.Deprecated.WithLongReplacement *'.PHP_EOL; + $expected .= ' Fixtures.Deprecated.WithoutReplacement *'.PHP_EOL; + $expected .= ' Fixtures.Deprecated.WithReplacement *'.PHP_EOL; + $expected .= ' Fixtures.Deprecated.WithReplacementContainingLinuxNewlines *'.PHP_EOL; + $expected .= ' Fixtures.Deprecated.WithReplacementContainingNewlines *'.PHP_EOL; + $expected .= ' Fixtures.SetProperty.AllowedAsDeclared'.PHP_EOL; + $expected .= ' Fixtures.SetProperty.AllowedViaMagicMethod'.PHP_EOL; + $expected .= ' Fixtures.SetProperty.AllowedViaStdClass'.PHP_EOL; + $expected .= ' Fixtures.SetProperty.NotAllowedViaAttribute'.PHP_EOL.PHP_EOL; + + $expected .= '* Sniffs marked with an asterix are deprecated.'.PHP_EOL; + + $this->expectOutputString($expected); + + $ruleset->explain(); + + }//end testExplainWithDeprecatedSniffs() + + /** * Test that each standard passed on the command-line is explained separately. *