From 0686756b42bddcb5965c5932f60793251fd572a8 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 21 Sep 2023 14:58:22 -0400 Subject: [PATCH 01/11] Improve handling of disable/enable/ignore directives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current method, listing codes to disable and a list of exceptions to that list, still has trouble with some cases. For example, disabling a standard, re-enabling a category within that standard, then ignoring or disabling a sniff within that category cannot be handled. We'd need a list of exceptions to the exceptions, and possibly a list of exceptions to that list too, and figuring out how to keep those lists up to date as new directives are encountered could prove to be confusing. Since the standard→category→sniff→code hierarchy is supposed to be thought of as a tree, let's store the ignore list that way instead. Manipulating the branches of the tree is straightforward no matter what directives are encountered. In this implementation I've favored speed over space: there are cases where we could prune a subtree that would evaluate to "ignore" or "don't ignore" for any possible input, but detecting that doesn't seem worth the time when it's not likely there will be so many enable or disable directives that the wasted space will be a problem. Fixes #111 --- src/Files/File.php | 31 +--- src/Tokenizers/Tokenizer.php | 101 ++++------- src/Util/IgnoreList.php | 168 ++++++++++++++++++ tests/Core/ErrorSuppressionTest.php | 64 +++++++ tests/Core/IgnoreListTest.php | 253 ++++++++++++++++++++++++++++ 5 files changed, 522 insertions(+), 95 deletions(-) create mode 100644 src/Util/IgnoreList.php create mode 100644 tests/Core/IgnoreListTest.php diff --git a/src/Files/File.php b/src/Files/File.php index ff91acb970..cfd2dd68c6 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -858,7 +858,7 @@ public function addFixableWarning( protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable) { // Check if this line is ignoring all message codes. - if (isset($this->tokenizer->ignoredLines[$line]['.all']) === true) { + if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->isAll() === true) { return false; } @@ -892,32 +892,9 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s ]; }//end if - if (isset($this->tokenizer->ignoredLines[$line]) === true) { - // Check if this line is ignoring this specific message. - $ignored = false; - foreach ($checkCodes as $checkCode) { - if (isset($this->tokenizer->ignoredLines[$line][$checkCode]) === true) { - $ignored = true; - break; - } - } - - // If it is ignored, make sure there is no exception in place. - if ($ignored === true - && isset($this->tokenizer->ignoredLines[$line]['.except']) === true - ) { - foreach ($checkCodes as $checkCode) { - if (isset($this->tokenizer->ignoredLines[$line]['.except'][$checkCode]) === true) { - $ignored = false; - break; - } - } - } - - if ($ignored === true) { - return false; - } - }//end if + if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->check($sniffCode) === true) { + return false; + } $includeAll = true; if ($this->configCache['cache'] === false diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index 412efba9c3..e5b8d8510a 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -11,6 +11,7 @@ use PHP_CodeSniffer\Exceptions\TokenizerException; use PHP_CodeSniffer\Util\Common; +use PHP_CodeSniffer\Util\IgnoreList; use PHP_CodeSniffer\Util\Tokens; use PHP_CodeSniffer\Util\Writers\StatusWriter; @@ -175,6 +176,7 @@ private function createPositionMap() $lineNumber = 1; $eolLen = strlen($this->eolChar); $ignoring = null; + $ignoreAll = IgnoreList::ignoringAll(); $inTests = defined('PHP_CODESNIFFER_IN_TESTS'); $checkEncoding = false; @@ -349,7 +351,7 @@ private function createPositionMap() if (substr($commentTextLower, 0, 9) === 'phpcs:set') { // Ignore standards for complete lines that change sniff settings. if ($lineHasOtherTokens === false) { - $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true]; + $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreAll; } // Need to maintain case here, to get the correct sniff code. @@ -372,42 +374,28 @@ private function createPositionMap() } else if (substr($commentTextLower, 0, 13) === 'phpcs:disable') { if ($lineHasOtherContent === false) { // Completely ignore the comment line. - $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true]; - } - - if ($ignoring === null) { - $ignoring = []; + $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreAll; } $disabledSniffs = []; $additionalText = substr($commentText, 14); if (empty($additionalText) === true) { - $ignoring = ['.all' => true]; + $ignoring = $ignoreAll; } else { + if ($ignoring === null) { + $ignoring = IgnoreList::ignoringNone(); + } else { + $ignoring = clone $ignoring; + } + $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $sniffCode = trim($sniffCode); $disabledSniffs[$sniffCode] = true; - $ignoring[$sniffCode] = true; - - // This newly disabled sniff might be disabling an existing - // enabled exception that we are tracking. - if (isset($ignoring['.except']) === true) { - foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) { - if ($ignoredSniffCode === $sniffCode - || strpos($ignoredSniffCode, $sniffCode.'.') === 0 - ) { - unset($ignoring['.except'][$ignoredSniffCode]); - } - } - - if (empty($ignoring['.except']) === true) { - unset($ignoring['.except']); - } - } - }//end foreach - }//end if + $ignoring->set($sniffCode, true); + } + } $this->tokens[$i]['code'] = T_PHPCS_DISABLE; $this->tokens[$i]['type'] = 'T_PHPCS_DISABLE'; @@ -420,49 +408,22 @@ private function createPositionMap() if (empty($additionalText) === true) { $ignoring = null; } else { - $parts = explode(',', $additionalText); + $ignoring = clone $ignoring; + $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $sniffCode = trim($sniffCode); $enabledSniffs[$sniffCode] = true; + $ignoring->set($sniffCode, false); + } - // This new enabled sniff might remove previously disabled - // sniffs if it is actually a standard or category of sniffs. - foreach (array_keys($ignoring) as $ignoredSniffCode) { - if ($ignoredSniffCode === $sniffCode - || strpos($ignoredSniffCode, $sniffCode.'.') === 0 - ) { - unset($ignoring[$ignoredSniffCode]); - } - } - - // This new enabled sniff might be able to clear up - // previously enabled sniffs if it is actually a standard or - // category of sniffs. - if (isset($ignoring['.except']) === true) { - foreach (array_keys($ignoring['.except']) as $ignoredSniffCode) { - if ($ignoredSniffCode === $sniffCode - || strpos($ignoredSniffCode, $sniffCode.'.') === 0 - ) { - unset($ignoring['.except'][$ignoredSniffCode]); - } - } - } - }//end foreach - - if (empty($ignoring) === true) { + if ($ignoring->isEmpty() === true) { $ignoring = null; - } else { - if (isset($ignoring['.except']) === true) { - $ignoring['.except'] += $enabledSniffs; - } else { - $ignoring['.except'] = $enabledSniffs; - } } - }//end if + } if ($lineHasOtherContent === false) { // Completely ignore the comment line. - $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true]; + $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreAll; } else { // The comment is on the same line as the code it is ignoring, // so respect the new ignore rules. @@ -479,11 +440,19 @@ private function createPositionMap() $additionalText = substr($commentText, 13); if (empty($additionalText) === true) { - $ignoreRules = ['.all' => true]; + $ignoreRules = ['.all' => true]; + $lineIgnoring = $ignoreAll; } else { $parts = explode(',', $additionalText); + if ($ignoring === null) { + $lineIgnoring = IgnoreList::ignoringNone(); + } else { + $lineIgnoring = clone $ignoring; + } + foreach ($parts as $sniffCode) { $ignoreRules[trim($sniffCode)] = true; + $lineIgnoring->set($sniffCode, true); } } @@ -491,19 +460,15 @@ private function createPositionMap() $this->tokens[$i]['type'] = 'T_PHPCS_IGNORE'; $this->tokens[$i]['sniffCodes'] = $ignoreRules; - if ($ignoring !== null) { - $ignoreRules += $ignoring; - } - if ($lineHasOtherContent === false) { // Completely ignore the comment line, and set the following // line to include the ignore rules we've set. - $this->ignoredLines[$this->tokens[$i]['line']] = ['.all' => true]; - $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $ignoreRules; + $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreAll; + $this->ignoredLines[($this->tokens[$i]['line'] + 1)] = $lineIgnoring; } else { // The comment is on the same line as the code it is ignoring, // so respect the ignore rules it set. - $this->ignoredLines[$this->tokens[$i]['line']] = $ignoreRules; + $this->ignoredLines[$this->tokens[$i]['line']] = $lineIgnoring; } }//end if }//end if diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php new file mode 100644 index 0000000000..26ba8f4fbb --- /dev/null +++ b/src/Util/IgnoreList.php @@ -0,0 +1,168 @@ + + * @copyright 2023 Brad Jorsch + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Util; + +class IgnoreList +{ + + /** + * Ignore data. + * + * Data is a tree, standard → category → sniff → code. + * Each level may be a boolean indicating that everything underneath the branch is or is not ignored, or + * may have a `.default' key indicating the default status for any branches not in the tree. + * + * @var array|boolean + */ + private $data = [ '.default' => false ]; + + + /** + * Get an instance set to ignore nothing. + * + * @return static + */ + public static function ignoringNone() + { + return new self(); + + }//end ignoringNone() + + + /** + * Get an instance set to ignore everything. + * + * @return static + */ + public static function ignoringAll() + { + $ret = new self(); + $ret->data['.default'] = true; + return $ret; + + }//end ignoringAll() + + + /** + * Check whether a sniff code is ignored. + * + * @param string $code Partial or complete sniff code. + * + * @return bool + */ + public function check($code) + { + $data = $this->data; + $ret = $data['.default']; + foreach (explode('.', $code) as $part) { + if (isset($data[$part]) === false) { + break; + } + + $data = $data[$part]; + if (is_bool($data) === true) { + $ret = $data; + break; + } + + if (isset($data['.default']) === true) { + $ret = $data['.default']; + } + } + + return $ret; + + }//end check() + + + /** + * Set the ignore status for a sniff. + * + * @param string $code Partial or complete sniff code. + * @param bool $ignore Whether the specified sniff should be ignored. + * + * @return this + */ + public function set($code, $ignore) + { + $data = &$this->data; + $parts = explode('.', $code); + while (count($parts) > 1) { + $part = array_shift($parts); + if (isset($data[$part]) === false) { + $data[$part] = []; + } else if (is_bool($data[$part]) === true) { + $data[$part] = [ '.default' => $data[$part] ]; + } + + $data = &$data[$part]; + } + + $part = array_shift($parts); + $data[$part] = (bool) $ignore; + + return $this; + + }//end set() + + + /** + * Check if the list is empty. + * + * @return bool + */ + public function isEmpty() + { + $arrs = [ $this->data ]; + while ($arrs !== []) { + $arr = array_pop($arrs); + foreach ($arr as $v) { + if ($v === true) { + return false; + } + + if (is_array($v) === true) { + $arrs[] = $v; + } + } + } + + return true; + + }//end isEmpty() + + + /** + * Check if the list ignores everything. + * + * @return bool + */ + public function isAll() + { + $arrs = [ $this->data ]; + while ($arrs !== []) { + $arr = array_pop($arrs); + foreach ($arr as $v) { + if ($v === false) { + return false; + } + + if (is_array($v) === true) { + $arrs[] = $v; + } + } + } + + return true; + + }//end isAll() + + +}//end class diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 7d8c8366b0..a1b8eb4723 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -993,6 +993,70 @@ public static function dataEnableSelected() 'expectedErrors' => 1, 'expectedWarnings' => 2, ], + 'disable: two sniffs; enable: both sniffs; ignore: one of those sniffs (#3889)' => [ + 'code' => ' + // phpcs:disable Generic.PHP.LowerCaseConstant + // phpcs:disable Generic.Commenting.Todo + //TODO: write some code + $var = TRUE; + // phpcs:enable Generic.Commenting.Todo + // phpcs:enable Generic.PHP.LowerCaseConstant + + $var = FALSE; // phpcs:ignore Generic.PHP.LowerCaseConstant + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], + 'disable: two sniffs; enable: one sniff; ignore: enabled sniff' => [ + 'code' => ' + // phpcs:disable Generic.PHP.LowerCaseConstant + // phpcs:disable Generic.Commenting.Todo + //TODO: write some code + $var = TRUE; + // phpcs:enable Generic.PHP.LowerCaseConstant + + $var = FALSE; // phpcs:ignore Generic.PHP.LowerCaseConstant + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], + 'disable: two sniffs; enable: one sniff; ignore: category' => [ + 'code' => ' + // phpcs:disable Generic.PHP.LowerCaseConstant + // phpcs:disable Generic.Commenting.Todo + //TODO: write some code + $var = TRUE; + // phpcs:enable Generic.PHP.LowerCaseConstant + + $var = FALSE; // phpcs:ignore Generic.PHP + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], + 'disable: two sniffs; enable: category; ignore: sniff in category' => [ + 'code' => ' + // phpcs:disable Generic.PHP.LowerCaseConstant + // phpcs:disable Generic.Commenting.Todo + //TODO: write some code + $var = TRUE; + // phpcs:enable Generic.PHP + + $var = FALSE; // phpcs:ignore Generic.PHP.LowerCaseConstant + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], + 'disable: standard; enable: category in standard; disable: sniff in category' => [ + 'code' => ' + // phpcs:disable Generic + // phpcs:enable Generic.PHP + // phpcs:disable Generic.PHP.LowerCaseConstant + //TODO: write some code + $var = TRUE; + ', + 'expectedErrors' => 0, + 'expectedWarnings' => 0, + ], ]; }//end dataEnableSelected() diff --git a/tests/Core/IgnoreListTest.php b/tests/Core/IgnoreListTest.php new file mode 100644 index 0000000000..ea19db1831 --- /dev/null +++ b/tests/Core/IgnoreListTest.php @@ -0,0 +1,253 @@ + + * @copyright 2023 Brad Jorsch + * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core; + +use PHP_CodeSniffer\Util\IgnoreList; +use PHPUnit\Framework\TestCase; + +class IgnoreListTest extends TestCase +{ + + + /** + * Test ignoringNone() works. + * + * @covers PHP_CodeSniffer\Util\IgnoreList::ignoringNone + * @return void + */ + public function testIgnoringNoneWorks() + { + $ignoreList = IgnoreList::ignoringNone(); + $this->assertInstanceOf(IgnoreList::class, $ignoreList); + $this->assertFalse($ignoreList->check('Anything')); + + }//end testIgnoringNoneWorks() + + + /** + * Test ignoringAll() works. + * + * @covers PHP_CodeSniffer\Util\IgnoreList::ignoringAll + * @return void + */ + public function testIgnoringAllWorks() + { + $ignoreList = IgnoreList::ignoringAll(); + $this->assertInstanceOf(IgnoreList::class, $ignoreList); + $this->assertTrue($ignoreList->check('Anything')); + + }//end testIgnoringAllWorks() + + + /** + * Test isEmpty() and isAll(). + * + * @param IgnoreList $ignoreList IgnoreList to test. + * @param bool $expectEmpty Expected return value from isEmpty(). + * @param bool $expectAll Expected return value from isAll(). + * + * @return void + * + * @dataProvider dataIsEmptyAndAll + * @covers PHP_CodeSniffer\Util\IgnoreList::isEmpty + * @covers PHP_CodeSniffer\Util\IgnoreList::isAll + */ + public function testIsEmptyAndAll($ignoreList, $expectEmpty, $expectAll) + { + $this->assertSame($expectEmpty, $ignoreList->isEmpty()); + $this->assertSame($expectAll, $ignoreList->isAll()); + + }//end testIsEmptyAndAll() + + + /** + * Data provider. + * + * @see testIsEmptyAndAll() + * + * @return array + */ + public static function dataIsEmptyAndAll() + { + return [ + 'fresh list' => [ + new IgnoreList(), + true, + false, + ], + 'list from ignoringNone' => [ + IgnoreList::ignoringNone(), + true, + false, + ], + 'list from ignoringAll' => [ + IgnoreList::ignoringAll(), + false, + true, + ], + 'list from ignoringNone, something set to false' => [ + IgnoreList::ignoringNone()->set('Foo.Bar', false), + true, + false, + ], + 'list from ignoringNone, something set to true' => [ + IgnoreList::ignoringNone()->set('Foo.Bar', true), + false, + false, + ], + 'list from ignoringAll, something set to false' => [ + IgnoreList::ignoringAll()->set('Foo.Bar', false), + false, + false, + ], + 'list from ignoringAll, something set to true' => [ + IgnoreList::ignoringAll()->set('Foo.Bar', true), + false, + true, + ], + 'list from ignoringNone, something set to true then overridden' => [ + IgnoreList::ignoringNone()->set('Foo.Bar', true)->set('Foo', false), + true, + false, + ], + 'list from ignoringAll, something set to false then overridden' => [ + IgnoreList::ignoringAll()->set('Foo.Bar', false)->set('Foo', true), + false, + true, + ], + ]; + + }//end dataIsEmptyAndAll() + + + /** + * Test check() and set(). + * + * @param array $toSet Associative array of $code => $ignore to pass to set(). + * @param array $toCheck Associative array of $code => $expect to pass to check(). + * + * @return void + * + * @dataProvider dataCheckAndSet + * @covers PHP_CodeSniffer\Util\IgnoreList::check + * @covers PHP_CodeSniffer\Util\IgnoreList::set + */ + public function testCheckAndSet($toSet, $toCheck) + { + $ignoreList = new IgnoreList(); + foreach ($toSet as $code => $ignore) { + $this->assertSame($ignoreList, $ignoreList->set($code, $ignore)); + } + + foreach ($toCheck as $code => $expect) { + $this->assertSame($expect, $ignoreList->check($code)); + } + + }//end testCheckAndSet() + + + /** + * Data provider. + * + * @see testCheckAndSet() + * + * @return array + */ + public static function dataCheckAndSet() + { + return [ + 'set a code' => [ + ['Standard.Category.Sniff.Code' => true], + [ + 'Standard.Category.Sniff.Code' => true, + 'Standard.Category.Sniff.OtherCode' => false, + 'Standard.Category.OtherSniff.Code' => false, + 'Standard.OtherCategory.Sniff.Code' => false, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'set a sniff' => [ + ['Standard.Category.Sniff' => true], + [ + 'Standard.Category.Sniff.Code' => true, + 'Standard.Category.Sniff.OtherCode' => true, + 'Standard.Category.OtherSniff.Code' => false, + 'Standard.OtherCategory.Sniff.Code' => false, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'set a category' => [ + ['Standard.Category' => true], + [ + 'Standard.Category.Sniff.Code' => true, + 'Standard.Category.Sniff.OtherCode' => true, + 'Standard.Category.OtherSniff.Code' => true, + 'Standard.OtherCategory.Sniff.Code' => false, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'set a standard' => [ + ['Standard' => true], + [ + 'Standard.Category.Sniff.Code' => true, + 'Standard.Category.Sniff.OtherCode' => true, + 'Standard.Category.OtherSniff.Code' => true, + 'Standard.OtherCategory.Sniff.Code' => true, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'set a standard, unignore a sniff in it' => [ + [ + 'Standard' => true, + 'Standard.Category.Sniff' => false, + ], + [ + 'Standard.Category.Sniff.Code' => false, + 'Standard.Category.Sniff.OtherCode' => false, + 'Standard.Category.OtherSniff.Code' => true, + 'Standard.OtherCategory.Sniff.Code' => true, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'set a standard, unignore a category in it, ignore a sniff in that' => [ + [ + 'Standard' => true, + 'Standard.Category' => false, + 'Standard.Category.Sniff' => true, + ], + [ + 'Standard.Category.Sniff.Code' => true, + 'Standard.Category.Sniff.OtherCode' => true, + 'Standard.Category.OtherSniff.Code' => false, + 'Standard.OtherCategory.Sniff.Code' => true, + 'OtherStandard.Category.Sniff.Code' => false, + ], + ], + 'ignore some sniffs, then override some of those by unignoring the whole category' => [ + [ + 'Standard.Category1.Sniff1' => true, + 'Standard.Category1.Sniff2' => true, + 'Standard.Category2.Sniff1' => true, + 'Standard.Category2.Sniff2' => true, + 'Standard.Category1' => false, + ], + [ + 'Standard.Category1.Sniff1' => false, + 'Standard.Category1.Sniff2' => false, + 'Standard.Category2.Sniff1' => true, + 'Standard.Category2.Sniff2' => true, + ], + ], + ]; + + }//end dataCheckAndSet() + + +}//end class From 0f1366e722e9520020aef3aa6bf2c2dcb48f12b5 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Tue, 6 May 2025 10:15:11 -0600 Subject: [PATCH 02/11] Apply suggestions from code review --- src/Files/File.php | 4 +- src/Tokenizers/Tokenizer.php | 20 +-- src/Util/IgnoreList.php | 111 +++++++++----- tests/Core/ErrorSuppressionTest.php | 10 ++ .../IgnoreList/CheckAndSetTest.php} | 140 ++---------------- .../IgnoreList/GetInstanceIgnoringAllTest.php | 37 +++++ .../GetInstanceIgnoringNothingTest.php | 37 +++++ .../IgnoreList/GetNewInstanceFromTest.php | 42 ++++++ .../IgnoresNothingAndEverythingTest.php | 103 +++++++++++++ 9 files changed, 323 insertions(+), 181 deletions(-) rename tests/Core/{IgnoreListTest.php => Util/IgnoreList/CheckAndSetTest.php} (53%) create mode 100644 tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php create mode 100644 tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php create mode 100644 tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php create mode 100644 tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php diff --git a/src/Files/File.php b/src/Files/File.php index cfd2dd68c6..e092c687de 100644 --- a/src/Files/File.php +++ b/src/Files/File.php @@ -858,7 +858,7 @@ public function addFixableWarning( protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable) { // Check if this line is ignoring all message codes. - if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->isAll() === true) { + if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->ignoresEverything() === true) { return false; } @@ -892,7 +892,7 @@ protected function addMessage($error, $message, $line, $column, $code, $data, $s ]; }//end if - if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->check($sniffCode) === true) { + if (isset($this->tokenizer->ignoredLines[$line]) === true && $this->tokenizer->ignoredLines[$line]->isIgnored($sniffCode) === true) { return false; } diff --git a/src/Tokenizers/Tokenizer.php b/src/Tokenizers/Tokenizer.php index e5b8d8510a..4c7feae960 100644 --- a/src/Tokenizers/Tokenizer.php +++ b/src/Tokenizers/Tokenizer.php @@ -176,7 +176,7 @@ private function createPositionMap() $lineNumber = 1; $eolLen = strlen($this->eolChar); $ignoring = null; - $ignoreAll = IgnoreList::ignoringAll(); + $ignoreAll = IgnoreList::getInstanceIgnoringAll(); $inTests = defined('PHP_CODESNIFFER_IN_TESTS'); $checkEncoding = false; @@ -383,11 +383,7 @@ private function createPositionMap() if (empty($additionalText) === true) { $ignoring = $ignoreAll; } else { - if ($ignoring === null) { - $ignoring = IgnoreList::ignoringNone(); - } else { - $ignoring = clone $ignoring; - } + $ignoring = IgnoreList::getNewInstanceFrom($ignoring); $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { @@ -408,7 +404,7 @@ private function createPositionMap() if (empty($additionalText) === true) { $ignoring = null; } else { - $ignoring = clone $ignoring; + $ignoring = IgnoreList::getNewInstanceFrom($ignoring); $parts = explode(',', $additionalText); foreach ($parts as $sniffCode) { $sniffCode = trim($sniffCode); @@ -416,7 +412,7 @@ private function createPositionMap() $ignoring->set($sniffCode, false); } - if ($ignoring->isEmpty() === true) { + if ($ignoring->ignoresNothing() === true) { $ignoring = null; } } @@ -443,12 +439,8 @@ private function createPositionMap() $ignoreRules = ['.all' => true]; $lineIgnoring = $ignoreAll; } else { - $parts = explode(',', $additionalText); - if ($ignoring === null) { - $lineIgnoring = IgnoreList::ignoringNone(); - } else { - $lineIgnoring = clone $ignoring; - } + $parts = explode(',', $additionalText); + $lineIgnoring = IgnoreList::getNewInstanceFrom($ignoring); foreach ($parts as $sniffCode) { $ignoreRules[trim($sniffCode)] = true; diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php index 26ba8f4fbb..3c42a65199 100644 --- a/src/Util/IgnoreList.php +++ b/src/Util/IgnoreList.php @@ -2,14 +2,23 @@ /** * Class to manage a list of sniffs to ignore. * - * @author Brad Jorsch - * @copyright 2023 Brad Jorsch - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @copyright 2025 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ namespace PHP_CodeSniffer\Util; -class IgnoreList +/** + * Class to manage a list of sniffs to ignore. + * + * --------------------------------------------------------------------------------------------- + * This class is intended for internal use only and is not part of the public API. + * This also means that it has no promise of backward compatibility. Use at your own risk. + * --------------------------------------------------------------------------------------------- + * + * @internal + */ +final class IgnoreList { /** @@ -19,7 +28,7 @@ class IgnoreList * Each level may be a boolean indicating that everything underneath the branch is or is not ignored, or * may have a `.default' key indicating the default status for any branches not in the tree. * - * @var array|boolean + * @var array>>> */ private $data = [ '.default' => false ]; @@ -27,27 +36,47 @@ class IgnoreList /** * Get an instance set to ignore nothing. * - * @return static + * @return IgnoreList */ - public static function ignoringNone() + public static function getInstanceIgnoringNothing() { return new self(); - }//end ignoringNone() + }//end getInstanceIgnoringNothing() /** * Get an instance set to ignore everything. * - * @return static + * @return IgnoreList + */ + public static function getInstanceIgnoringAll() + { + $instance = new self(); + $instance->data['.default'] = true; + return $instance; + + }//end getInstanceIgnoringAll() + + + /** + * Get a new instance based on an existing instance. + * + * If passed null, creates a new instance that ignores nothing. + * + * @param IgnoreList|null $ignoreList List to clone. + * + * @return IgnoreList */ - public static function ignoringAll() + public static function getNewInstanceFrom(?IgnoreList $ignoreList) { - $ret = new self(); - $ret->data['.default'] = true; - return $ret; + if ($ignoreList === null) { + return self::getInstanceIgnoringNothing(); + } + + return clone $ignoreList; - }//end ignoringAll() + }//end getNewInstanceFrom() /** @@ -57,10 +86,10 @@ public static function ignoringAll() * * @return bool */ - public function check($code) + public function isIgnored($code) { - $data = $this->data; - $ret = $data['.default']; + $data = $this->data; + $returnValue = $data['.default']; foreach (explode('.', $code) as $part) { if (isset($data[$part]) === false) { break; @@ -68,18 +97,18 @@ public function check($code) $data = $data[$part]; if (is_bool($data) === true) { - $ret = $data; + $returnValue = $data; break; } if (isset($data['.default']) === true) { - $ret = $data['.default']; + $returnValue = $data['.default']; } } - return $ret; + return $returnValue; - }//end check() + }//end isIgnored() /** @@ -88,7 +117,7 @@ public function check($code) * @param string $code Partial or complete sniff code. * @param bool $ignore Whether the specified sniff should be ignored. * - * @return this + * @return IgnoreList $this for chaining. */ public function set($code, $ignore) { @@ -114,29 +143,29 @@ public function set($code, $ignore) /** - * Check if the list is empty. + * Check if the list ignores nothing. * * @return bool */ - public function isEmpty() + public function ignoresNothing() { - $arrs = [ $this->data ]; - while ($arrs !== []) { - $arr = array_pop($arrs); - foreach ($arr as $v) { - if ($v === true) { + $arraysToProcess = [ $this->data ]; + while ($arraysToProcess !== []) { + $arrayBeingProcessed = array_pop($arraysToProcess); + foreach ($arrayBeingProcessed as $valueBeingProcessed) { + if ($valueBeingProcessed === true) { return false; } - if (is_array($v) === true) { - $arrs[] = $v; + if (is_array($valueBeingProcessed) === true) { + $arraysToProcess[] = $valueBeingProcessed; } } } return true; - }//end isEmpty() + }//end ignoresNothing() /** @@ -144,25 +173,25 @@ public function isEmpty() * * @return bool */ - public function isAll() + public function ignoresEverything() { - $arrs = [ $this->data ]; - while ($arrs !== []) { - $arr = array_pop($arrs); - foreach ($arr as $v) { - if ($v === false) { + $arraysToProcess = [ $this->data ]; + while ($arraysToProcess !== []) { + $arrayBeingProcessed = array_pop($arraysToProcess); + foreach ($arrayBeingProcessed as $valueBeingProcessed) { + if ($valueBeingProcessed === false) { return false; } - if (is_array($v) === true) { - $arrs[] = $v; + if (is_array($valueBeingProcessed) === true) { + $arraysToProcess[] = $valueBeingProcessed; } } } return true; - }//end isAll() + }//end ignoresEverything() }//end class diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index a1b8eb4723..223261fc72 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -1057,6 +1057,16 @@ public static function dataEnableSelected() 'expectedErrors' => 0, 'expectedWarnings' => 0, ], + 'disable: everything; enable: sniff' => [ + 'code' => ' + // phpcs:disable + // phpcs:enable Generic.PHP.LowerCaseConstant + //TODO: write some code + $var = TRUE; + ', + 'expectedErrors' => 1, + 'expectedWarnings' => 0, + ], ]; }//end dataEnableSelected() diff --git a/tests/Core/IgnoreListTest.php b/tests/Core/Util/IgnoreList/CheckAndSetTest.php similarity index 53% rename from tests/Core/IgnoreListTest.php rename to tests/Core/Util/IgnoreList/CheckAndSetTest.php index ea19db1831..b3d2421405 100644 --- a/tests/Core/IgnoreListTest.php +++ b/tests/Core/Util/IgnoreList/CheckAndSetTest.php @@ -2,152 +2,44 @@ /** * Tests for the IgnoreList class. * - * @author Brad Jorsch - * @copyright 2023 Brad Jorsch - * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + * @copyright 2025 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence */ -namespace PHP_CodeSniffer\Tests\Core; +namespace PHP_CodeSniffer\Tests\Core\Util\IgnoreList; use PHP_CodeSniffer\Util\IgnoreList; use PHPUnit\Framework\TestCase; -class IgnoreListTest extends TestCase +/** + * Test isIgnored() and set(). + * + * @covers PHP_CodeSniffer\Util\IgnoreList::isIgnored + * @covers PHP_CodeSniffer\Util\IgnoreList::set + */ +class CheckAndSetTest extends TestCase { /** - * Test ignoringNone() works. - * - * @covers PHP_CodeSniffer\Util\IgnoreList::ignoringNone - * @return void - */ - public function testIgnoringNoneWorks() - { - $ignoreList = IgnoreList::ignoringNone(); - $this->assertInstanceOf(IgnoreList::class, $ignoreList); - $this->assertFalse($ignoreList->check('Anything')); - - }//end testIgnoringNoneWorks() - - - /** - * Test ignoringAll() works. - * - * @covers PHP_CodeSniffer\Util\IgnoreList::ignoringAll - * @return void - */ - public function testIgnoringAllWorks() - { - $ignoreList = IgnoreList::ignoringAll(); - $this->assertInstanceOf(IgnoreList::class, $ignoreList); - $this->assertTrue($ignoreList->check('Anything')); - - }//end testIgnoringAllWorks() - - - /** - * Test isEmpty() and isAll(). - * - * @param IgnoreList $ignoreList IgnoreList to test. - * @param bool $expectEmpty Expected return value from isEmpty(). - * @param bool $expectAll Expected return value from isAll(). - * - * @return void - * - * @dataProvider dataIsEmptyAndAll - * @covers PHP_CodeSniffer\Util\IgnoreList::isEmpty - * @covers PHP_CodeSniffer\Util\IgnoreList::isAll - */ - public function testIsEmptyAndAll($ignoreList, $expectEmpty, $expectAll) - { - $this->assertSame($expectEmpty, $ignoreList->isEmpty()); - $this->assertSame($expectAll, $ignoreList->isAll()); - - }//end testIsEmptyAndAll() - - - /** - * Data provider. - * - * @see testIsEmptyAndAll() - * - * @return array - */ - public static function dataIsEmptyAndAll() - { - return [ - 'fresh list' => [ - new IgnoreList(), - true, - false, - ], - 'list from ignoringNone' => [ - IgnoreList::ignoringNone(), - true, - false, - ], - 'list from ignoringAll' => [ - IgnoreList::ignoringAll(), - false, - true, - ], - 'list from ignoringNone, something set to false' => [ - IgnoreList::ignoringNone()->set('Foo.Bar', false), - true, - false, - ], - 'list from ignoringNone, something set to true' => [ - IgnoreList::ignoringNone()->set('Foo.Bar', true), - false, - false, - ], - 'list from ignoringAll, something set to false' => [ - IgnoreList::ignoringAll()->set('Foo.Bar', false), - false, - false, - ], - 'list from ignoringAll, something set to true' => [ - IgnoreList::ignoringAll()->set('Foo.Bar', true), - false, - true, - ], - 'list from ignoringNone, something set to true then overridden' => [ - IgnoreList::ignoringNone()->set('Foo.Bar', true)->set('Foo', false), - true, - false, - ], - 'list from ignoringAll, something set to false then overridden' => [ - IgnoreList::ignoringAll()->set('Foo.Bar', false)->set('Foo', true), - false, - true, - ], - ]; - - }//end dataIsEmptyAndAll() - - - /** - * Test check() and set(). + * Test isIgnored() and set(). * - * @param array $toSet Associative array of $code => $ignore to pass to set(). - * @param array $toCheck Associative array of $code => $expect to pass to check(). + * @param array $toSet Associative array of $code => $ignore to pass to set(). + * @param array $toCheck Associative array of $code => $expect to pass to isIgnored(). * * @return void * * @dataProvider dataCheckAndSet - * @covers PHP_CodeSniffer\Util\IgnoreList::check - * @covers PHP_CodeSniffer\Util\IgnoreList::set */ public function testCheckAndSet($toSet, $toCheck) { $ignoreList = new IgnoreList(); foreach ($toSet as $code => $ignore) { - $this->assertSame($ignoreList, $ignoreList->set($code, $ignore)); + $this->assertSame($ignoreList, $ignoreList->set($code, $ignore), 'Set method returned $this'); } foreach ($toCheck as $code => $expect) { - $this->assertSame($expect, $ignoreList->check($code)); + $this->assertSame($expect, $ignoreList->isIgnored($code), "$code is ignored"); } }//end testCheckAndSet() @@ -158,7 +50,7 @@ public function testCheckAndSet($toSet, $toCheck) * * @see testCheckAndSet() * - * @return array + * @return array>> */ public static function dataCheckAndSet() { diff --git a/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php b/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php new file mode 100644 index 0000000000..9ba935534d --- /dev/null +++ b/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php @@ -0,0 +1,37 @@ +assertInstanceOf(IgnoreList::class, $ignoreList); + $this->assertTrue($ignoreList->isIgnored('Anything')); + + }//end testGetInstanceIgnoringAllWorks() + + +}//end class diff --git a/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php b/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php new file mode 100644 index 0000000000..aeefc98181 --- /dev/null +++ b/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php @@ -0,0 +1,37 @@ +assertInstanceOf(IgnoreList::class, $ignoreList); + $this->assertFalse($ignoreList->isIgnored('Anything')); + + }//end testGetInstanceIgnoringNothingWorks() + + +}//end class diff --git a/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php b/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php new file mode 100644 index 0000000000..d0b20f2578 --- /dev/null +++ b/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php @@ -0,0 +1,42 @@ +assertTrue($ignoreList->ignoresNothing(), 'Passing null returned an instance ignoring nothing'); + + $ignoreList->set('Foo.Bar', true); + $ignoreList2 = IgnoreList::getNewInstanceFrom($ignoreList); + $this->assertNotSame($ignoreList, $ignoreList2, 'Passing an instance returns a different instance'); + + $this->assertTrue($ignoreList2->isIgnored('Foo.Bar'), 'New instance ignores the same as the old one'); + + }//end testGetNewInstanceFrom() + + +}//end class diff --git a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php new file mode 100644 index 0000000000..64ebef3cd4 --- /dev/null +++ b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php @@ -0,0 +1,103 @@ +assertSame($expectIgnoresNothing, $ignoreList->ignoresNothing(), 'Ignores nothing'); + $this->assertSame($expectIgnoresEverything, $ignoreList->ignoresEverything(), 'Ignores everything'); + + }//end testIgnoresNothingAndEverything() + + + /** + * Data provider. + * + * @see testIgnoresNothingAndEverything() + * + * @return array> + */ + public static function dataIgnoresNothingAndEverything() + { + return [ + 'fresh list' => [ + new IgnoreList(), + true, + false, + ], + 'list from getInstanceIgnoringNothing' => [ + IgnoreList::getInstanceIgnoringNothing(), + true, + false, + ], + 'list from getInstanceIgnoringAll' => [ + IgnoreList::getInstanceIgnoringAll(), + false, + true, + ], + 'list from getInstanceIgnoringNothing, something set to false' => [ + IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', false), + true, + false, + ], + 'list from getInstanceIgnoringNothing, something set to true' => [ + IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true), + false, + false, + ], + 'list from getInstanceIgnoringAll, something set to false' => [ + IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false), + false, + false, + ], + 'list from getInstanceIgnoringAll, something set to true' => [ + IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', true), + false, + true, + ], + 'list from getInstanceIgnoringNothing, something set to true then overridden' => [ + IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true)->set('Foo', false), + true, + false, + ], + 'list from getInstanceIgnoringAll, something set to false then overridden' => [ + IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false)->set('Foo', true), + false, + true, + ], + ]; + + }//end dataIgnoresNothingAndEverything() + + +}//end class From e51e7c63b9e4d67352f14d726263954ae6b28c13 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Tue, 6 May 2025 11:43:43 -0600 Subject: [PATCH 03/11] Use FQCNs in doc comments --- src/Util/IgnoreList.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php index 3c42a65199..6fd9881b86 100644 --- a/src/Util/IgnoreList.php +++ b/src/Util/IgnoreList.php @@ -36,7 +36,7 @@ final class IgnoreList /** * Get an instance set to ignore nothing. * - * @return IgnoreList + * @return \PHP_CodeSniffer\Util\IgnoreList */ public static function getInstanceIgnoringNothing() { @@ -48,7 +48,7 @@ public static function getInstanceIgnoringNothing() /** * Get an instance set to ignore everything. * - * @return IgnoreList + * @return \PHP_CodeSniffer\Util\IgnoreList */ public static function getInstanceIgnoringAll() { @@ -64,9 +64,9 @@ public static function getInstanceIgnoringAll() * * If passed null, creates a new instance that ignores nothing. * - * @param IgnoreList|null $ignoreList List to clone. + * @param \PHP_CodeSniffer\Util\IgnoreList|null $ignoreList List to clone. * - * @return IgnoreList + * @return \PHP_CodeSniffer\Util\IgnoreList */ public static function getNewInstanceFrom(?IgnoreList $ignoreList) { @@ -117,7 +117,7 @@ public function isIgnored($code) * @param string $code Partial or complete sniff code. * @param bool $ignore Whether the specified sniff should be ignored. * - * @return IgnoreList $this for chaining. + * @return \PHP_CodeSniffer\Util\IgnoreList $this for chaining. */ public function set($code, $ignore) { From 29e2f494bde73a1d895a74b3345ba60f169cff84 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Tue, 6 May 2025 11:45:13 -0600 Subject: [PATCH 04/11] Missed one arrow alignment --- tests/Core/ErrorSuppressionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Core/ErrorSuppressionTest.php b/tests/Core/ErrorSuppressionTest.php index 223261fc72..793574b83a 100644 --- a/tests/Core/ErrorSuppressionTest.php +++ b/tests/Core/ErrorSuppressionTest.php @@ -1057,7 +1057,7 @@ public static function dataEnableSelected() 'expectedErrors' => 0, 'expectedWarnings' => 0, ], - 'disable: everything; enable: sniff' => [ + 'disable: everything; enable: sniff' => [ 'code' => ' // phpcs:disable // phpcs:enable Generic.PHP.LowerCaseConstant From 57c222ea081e4391997866c808fdf4823ee795d6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:18:43 +0200 Subject: [PATCH 05/11] IgnoreList: add private constructor --- src/Util/IgnoreList.php | 9 +++++++++ tests/Core/Util/IgnoreList/CheckAndSetTest.php | 2 +- .../Util/IgnoreList/IgnoresNothingAndEverythingTest.php | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php index 6fd9881b86..1f4c44df68 100644 --- a/src/Util/IgnoreList.php +++ b/src/Util/IgnoreList.php @@ -33,6 +33,15 @@ final class IgnoreList private $data = [ '.default' => false ]; + /** + * Prohibit direct instantiation of this class. Use the static `get[New]Instance*()` entry point methods instead. + */ + private function __construct() + { + + }//end __construct() + + /** * Get an instance set to ignore nothing. * diff --git a/tests/Core/Util/IgnoreList/CheckAndSetTest.php b/tests/Core/Util/IgnoreList/CheckAndSetTest.php index b3d2421405..d39eee1024 100644 --- a/tests/Core/Util/IgnoreList/CheckAndSetTest.php +++ b/tests/Core/Util/IgnoreList/CheckAndSetTest.php @@ -33,7 +33,7 @@ class CheckAndSetTest extends TestCase */ public function testCheckAndSet($toSet, $toCheck) { - $ignoreList = new IgnoreList(); + $ignoreList = IgnoreList::getNewInstanceFrom(null); foreach ($toSet as $code => $ignore) { $this->assertSame($ignoreList, $ignoreList->set($code, $ignore), 'Set method returned $this'); } diff --git a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php index 64ebef3cd4..3aca0c654f 100644 --- a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php +++ b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php @@ -51,7 +51,7 @@ public static function dataIgnoresNothingAndEverything() { return [ 'fresh list' => [ - new IgnoreList(), + IgnoreList::getNewInstanceFrom(null), true, false, ], From cfe945f1059965c8a2cbd53c051f0d8b3af13b8e Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:18:58 +0200 Subject: [PATCH 06/11] IgnoreList: minor change in method order --- src/Util/IgnoreList.php | 62 ++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php index 1f4c44df68..85b98b1519 100644 --- a/src/Util/IgnoreList.php +++ b/src/Util/IgnoreList.php @@ -88,6 +88,37 @@ public static function getNewInstanceFrom(?IgnoreList $ignoreList) }//end getNewInstanceFrom() + /** + * Set the ignore status for a sniff. + * + * @param string $code Partial or complete sniff code. + * @param bool $ignore Whether the specified sniff should be ignored. + * + * @return \PHP_CodeSniffer\Util\IgnoreList $this for chaining. + */ + public function set($code, $ignore) + { + $data = &$this->data; + $parts = explode('.', $code); + while (count($parts) > 1) { + $part = array_shift($parts); + if (isset($data[$part]) === false) { + $data[$part] = []; + } else if (is_bool($data[$part]) === true) { + $data[$part] = [ '.default' => $data[$part] ]; + } + + $data = &$data[$part]; + } + + $part = array_shift($parts); + $data[$part] = (bool) $ignore; + + return $this; + + }//end set() + + /** * Check whether a sniff code is ignored. * @@ -120,37 +151,6 @@ public function isIgnored($code) }//end isIgnored() - /** - * Set the ignore status for a sniff. - * - * @param string $code Partial or complete sniff code. - * @param bool $ignore Whether the specified sniff should be ignored. - * - * @return \PHP_CodeSniffer\Util\IgnoreList $this for chaining. - */ - public function set($code, $ignore) - { - $data = &$this->data; - $parts = explode('.', $code); - while (count($parts) > 1) { - $part = array_shift($parts); - if (isset($data[$part]) === false) { - $data[$part] = []; - } else if (is_bool($data[$part]) === true) { - $data[$part] = [ '.default' => $data[$part] ]; - } - - $data = &$data[$part]; - } - - $part = array_shift($parts); - $data[$part] = (bool) $ignore; - - return $this; - - }//end set() - - /** * Check if the list ignores nothing. * From 2d934b7ed10ffd36b5f9d3aab2b4010e7dd52a75 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:29:06 +0200 Subject: [PATCH 07/11] IgnoresNothingAndEverythingTest: minor doc fixes --- .../Util/IgnoreList/IgnoresNothingAndEverythingTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php index 3aca0c654f..40bcccf946 100644 --- a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php +++ b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php @@ -24,9 +24,9 @@ class IgnoresNothingAndEverythingTest extends TestCase /** * Test ignoresNothing() and ignoresEverything(). * - * @param IgnoreList $ignoreList IgnoreList to test. - * @param bool $expectIgnoresNothing Expected return value from ignoresNothing(). - * @param bool $expectIgnoresEverything Expected return value from ignoresEverything(). + * @param \PHP_CodeSniffer\Util\IgnoreList $ignoreList IgnoreList to test. + * @param bool $expectIgnoresNothing Expected return value from ignoresNothing(). + * @param bool $expectIgnoresEverything Expected return value from ignoresEverything(). * * @return void * @@ -45,7 +45,7 @@ public function testIgnoresNothingAndEverything($ignoreList, $expectIgnoresNothi * * @see testIgnoresNothingAndEverything() * - * @return array> + * @return array> */ public static function dataIgnoresNothingAndEverything() { From f8eda67d6bf3ef379014318be9c42055e8d96368 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:33:46 +0200 Subject: [PATCH 08/11] IgnoreList: make test classes `final` --- tests/Core/Util/IgnoreList/CheckAndSetTest.php | 2 +- tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php | 2 +- tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php | 2 +- tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php | 2 +- tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Core/Util/IgnoreList/CheckAndSetTest.php b/tests/Core/Util/IgnoreList/CheckAndSetTest.php index d39eee1024..dc2a0bc13b 100644 --- a/tests/Core/Util/IgnoreList/CheckAndSetTest.php +++ b/tests/Core/Util/IgnoreList/CheckAndSetTest.php @@ -17,7 +17,7 @@ * @covers PHP_CodeSniffer\Util\IgnoreList::isIgnored * @covers PHP_CodeSniffer\Util\IgnoreList::set */ -class CheckAndSetTest extends TestCase +final class CheckAndSetTest extends TestCase { diff --git a/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php b/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php index 9ba935534d..1b4515ddd9 100644 --- a/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php +++ b/tests/Core/Util/IgnoreList/GetInstanceIgnoringAllTest.php @@ -16,7 +16,7 @@ * * @covers PHP_CodeSniffer\Util\IgnoreList::getInstanceIgnoringAll */ -class GetInstanceIgnoringAllTest extends TestCase +final class GetInstanceIgnoringAllTest extends TestCase { diff --git a/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php b/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php index aeefc98181..418539418b 100644 --- a/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php +++ b/tests/Core/Util/IgnoreList/GetInstanceIgnoringNothingTest.php @@ -16,7 +16,7 @@ * * @covers PHP_CodeSniffer\Util\IgnoreList::getInstanceIgnoringNothing */ -class GetInstanceIgnoringNothingTest extends TestCase +final class GetInstanceIgnoringNothingTest extends TestCase { diff --git a/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php b/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php index d0b20f2578..23837ab533 100644 --- a/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php +++ b/tests/Core/Util/IgnoreList/GetNewInstanceFromTest.php @@ -16,7 +16,7 @@ * * @covers PHP_CodeSniffer\Util\IgnoreList::getNewInstanceFrom */ -class GetNewInstanceFromTest extends TestCase +final class GetNewInstanceFromTest extends TestCase { diff --git a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php index 40bcccf946..36b8569ba0 100644 --- a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php +++ b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php @@ -17,7 +17,7 @@ * @covers PHP_CodeSniffer\Util\IgnoreList::ignoresNothing * @covers PHP_CodeSniffer\Util\IgnoreList::ignoresEverything */ -class IgnoresNothingAndEverythingTest extends TestCase +final class IgnoresNothingAndEverythingTest extends TestCase { From 69d7d32aff7c5c6d7a168716d70bb7375c381a53 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:35:12 +0200 Subject: [PATCH 09/11] IgnoreList: minor whitespace fixes --- src/Util/IgnoreList.php | 8 ++++---- tests/Core/Util/IgnoreList/CheckAndSetTest.php | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Util/IgnoreList.php b/src/Util/IgnoreList.php index 85b98b1519..14cf50c5ce 100644 --- a/src/Util/IgnoreList.php +++ b/src/Util/IgnoreList.php @@ -30,7 +30,7 @@ final class IgnoreList * * @var array>>> */ - private $data = [ '.default' => false ]; + private $data = ['.default' => false]; /** @@ -105,7 +105,7 @@ public function set($code, $ignore) if (isset($data[$part]) === false) { $data[$part] = []; } else if (is_bool($data[$part]) === true) { - $data[$part] = [ '.default' => $data[$part] ]; + $data[$part] = ['.default' => $data[$part]]; } $data = &$data[$part]; @@ -158,7 +158,7 @@ public function isIgnored($code) */ public function ignoresNothing() { - $arraysToProcess = [ $this->data ]; + $arraysToProcess = [$this->data]; while ($arraysToProcess !== []) { $arrayBeingProcessed = array_pop($arraysToProcess); foreach ($arrayBeingProcessed as $valueBeingProcessed) { @@ -184,7 +184,7 @@ public function ignoresNothing() */ public function ignoresEverything() { - $arraysToProcess = [ $this->data ]; + $arraysToProcess = [$this->data]; while ($arraysToProcess !== []) { $arrayBeingProcessed = array_pop($arraysToProcess); foreach ($arrayBeingProcessed as $valueBeingProcessed) { diff --git a/tests/Core/Util/IgnoreList/CheckAndSetTest.php b/tests/Core/Util/IgnoreList/CheckAndSetTest.php index dc2a0bc13b..8e7b13279c 100644 --- a/tests/Core/Util/IgnoreList/CheckAndSetTest.php +++ b/tests/Core/Util/IgnoreList/CheckAndSetTest.php @@ -24,8 +24,8 @@ final class CheckAndSetTest extends TestCase /** * Test isIgnored() and set(). * - * @param array $toSet Associative array of $code => $ignore to pass to set(). - * @param array $toCheck Associative array of $code => $expect to pass to isIgnored(). + * @param array $toSet Associative array of $code => $ignore to pass to set(). + * @param array $toCheck Associative array of $code => $expect to pass to isIgnored(). * * @return void * From 5ce0f2e9b10a8350fc56dfaf7404e4d5b41ce55c Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:40:36 +0200 Subject: [PATCH 10/11] IgnoresNothingAndEverythingTest: make data provider more descriptive --- .../IgnoresNothingAndEverythingTest.php | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php index 36b8569ba0..d77c1fa8cd 100644 --- a/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php +++ b/tests/Core/Util/IgnoreList/IgnoresNothingAndEverythingTest.php @@ -51,49 +51,49 @@ public static function dataIgnoresNothingAndEverything() { return [ 'fresh list' => [ - IgnoreList::getNewInstanceFrom(null), - true, - false, + 'ignoreList' => IgnoreList::getNewInstanceFrom(null), + 'expectIgnoresNothing' => true, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringNothing' => [ - IgnoreList::getInstanceIgnoringNothing(), - true, - false, + 'ignoreList' => IgnoreList::getInstanceIgnoringNothing(), + 'expectIgnoresNothing' => true, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringAll' => [ - IgnoreList::getInstanceIgnoringAll(), - false, - true, + 'ignoreList' => IgnoreList::getInstanceIgnoringAll(), + 'expectIgnoresNothing' => false, + 'expectIgnoresEverything' => true, ], 'list from getInstanceIgnoringNothing, something set to false' => [ - IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', false), - true, - false, + 'ignoreList' => IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', false), + 'expectIgnoresNothing' => true, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringNothing, something set to true' => [ - IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true), - false, - false, + 'ignoreList' => IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true), + 'expectIgnoresNothing' => false, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringAll, something set to false' => [ - IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false), - false, - false, + 'ignoreList' => IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false), + 'expectIgnoresNothing' => false, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringAll, something set to true' => [ - IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', true), - false, - true, + 'ignoreList' => IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', true), + 'expectIgnoresNothing' => false, + 'expectIgnoresEverything' => true, ], 'list from getInstanceIgnoringNothing, something set to true then overridden' => [ - IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true)->set('Foo', false), - true, - false, + 'ignoreList' => IgnoreList::getInstanceIgnoringNothing()->set('Foo.Bar', true)->set('Foo', false), + 'expectIgnoresNothing' => true, + 'expectIgnoresEverything' => false, ], 'list from getInstanceIgnoringAll, something set to false then overridden' => [ - IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false)->set('Foo', true), - false, - true, + 'ignoreList' => IgnoreList::getInstanceIgnoringAll()->set('Foo.Bar', false)->set('Foo', true), + 'expectIgnoresNothing' => false, + 'expectIgnoresEverything' => true, ], ]; From 8b6fe0ce14d72884bbde6d3e0aaa0ed917a1f183 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 17 Jun 2025 22:41:52 +0200 Subject: [PATCH 11/11] CheckAndSetTest: make data provider more descriptive --- .../Core/Util/IgnoreList/CheckAndSetTest.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/Core/Util/IgnoreList/CheckAndSetTest.php b/tests/Core/Util/IgnoreList/CheckAndSetTest.php index 8e7b13279c..3d521ae947 100644 --- a/tests/Core/Util/IgnoreList/CheckAndSetTest.php +++ b/tests/Core/Util/IgnoreList/CheckAndSetTest.php @@ -56,8 +56,8 @@ public static function dataCheckAndSet() { return [ 'set a code' => [ - ['Standard.Category.Sniff.Code' => true], - [ + 'toSet' => ['Standard.Category.Sniff.Code' => true], + 'toCheck' => [ 'Standard.Category.Sniff.Code' => true, 'Standard.Category.Sniff.OtherCode' => false, 'Standard.Category.OtherSniff.Code' => false, @@ -66,8 +66,8 @@ public static function dataCheckAndSet() ], ], 'set a sniff' => [ - ['Standard.Category.Sniff' => true], - [ + 'toSet' => ['Standard.Category.Sniff' => true], + 'toCheck' => [ 'Standard.Category.Sniff.Code' => true, 'Standard.Category.Sniff.OtherCode' => true, 'Standard.Category.OtherSniff.Code' => false, @@ -76,8 +76,8 @@ public static function dataCheckAndSet() ], ], 'set a category' => [ - ['Standard.Category' => true], - [ + 'toSet' => ['Standard.Category' => true], + 'toCheck' => [ 'Standard.Category.Sniff.Code' => true, 'Standard.Category.Sniff.OtherCode' => true, 'Standard.Category.OtherSniff.Code' => true, @@ -86,8 +86,8 @@ public static function dataCheckAndSet() ], ], 'set a standard' => [ - ['Standard' => true], - [ + 'toSet' => ['Standard' => true], + 'toCheck' => [ 'Standard.Category.Sniff.Code' => true, 'Standard.Category.Sniff.OtherCode' => true, 'Standard.Category.OtherSniff.Code' => true, @@ -96,11 +96,11 @@ public static function dataCheckAndSet() ], ], 'set a standard, unignore a sniff in it' => [ - [ + 'toSet' => [ 'Standard' => true, 'Standard.Category.Sniff' => false, ], - [ + 'toCheck' => [ 'Standard.Category.Sniff.Code' => false, 'Standard.Category.Sniff.OtherCode' => false, 'Standard.Category.OtherSniff.Code' => true, @@ -109,12 +109,12 @@ public static function dataCheckAndSet() ], ], 'set a standard, unignore a category in it, ignore a sniff in that' => [ - [ + 'toSet' => [ 'Standard' => true, 'Standard.Category' => false, 'Standard.Category.Sniff' => true, ], - [ + 'toCheck' => [ 'Standard.Category.Sniff.Code' => true, 'Standard.Category.Sniff.OtherCode' => true, 'Standard.Category.OtherSniff.Code' => false, @@ -123,14 +123,14 @@ public static function dataCheckAndSet() ], ], 'ignore some sniffs, then override some of those by unignoring the whole category' => [ - [ + 'toSet' => [ 'Standard.Category1.Sniff1' => true, 'Standard.Category1.Sniff2' => true, 'Standard.Category2.Sniff1' => true, 'Standard.Category2.Sniff2' => true, 'Standard.Category1' => false, ], - [ + 'toCheck' => [ 'Standard.Category1.Sniff1' => false, 'Standard.Category1.Sniff2' => false, 'Standard.Category2.Sniff1' => true,