From 20e3427c142a38362543f01ba2746217e665c67e Mon Sep 17 00:00:00 2001 From: Juliette <663378+jrfnl@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:28:17 +0100 Subject: [PATCH] Ruleset::populateTokenListeners(): add tests (#757) --- tests/Core/Ruleset/ExplainTest.php | 7 +- .../InvalidSniffs/RegisterNoArraySniff.php | 25 + .../ValidSniffs/RegisterEmptyArraySniff.php | 25 + ...ulateTokenListenersRegisterNoArrayTest.xml | 8 + .../Ruleset/PopulateTokenListenersTest.php | 451 ++++++++++++++++++ .../Ruleset/PopulateTokenListenersTest.xml | 45 ++ ...ssRulesetAutoExpandSniffsDirectoryTest.xml | 4 +- tests/Core/Ruleset/ProcessRulesetTest.php | 1 + .../Ruleset/ShowSniffDeprecationsTest.xml | 1 + 9 files changed, 563 insertions(+), 4 deletions(-) create mode 100644 tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffs/RegisterNoArraySniff.php create mode 100644 tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/ValidSniffs/RegisterEmptyArraySniff.php create mode 100644 tests/Core/Ruleset/PopulateTokenListenersRegisterNoArrayTest.xml create mode 100644 tests/Core/Ruleset/PopulateTokenListenersTest.php create mode 100644 tests/Core/Ruleset/PopulateTokenListenersTest.xml diff --git a/tests/Core/Ruleset/ExplainTest.php b/tests/Core/Ruleset/ExplainTest.php index caf9f4b3ce..6b0de0a256 100644 --- a/tests/Core/Ruleset/ExplainTest.php +++ b/tests/Core/Ruleset/ExplainTest.php @@ -185,9 +185,9 @@ public function testExplainWithDeprecatedSniffs() $ruleset = new Ruleset($config); $expected = PHP_EOL; - $expected .= 'The ShowSniffDeprecationsTest standard contains 10 sniffs'.PHP_EOL.PHP_EOL; + $expected .= 'The ShowSniffDeprecationsTest standard contains 11 sniffs'.PHP_EOL.PHP_EOL; - $expected .= 'TestStandard (10 sniffs)'.PHP_EOL; + $expected .= 'TestStandard (11 sniffs)'.PHP_EOL; $expected .= '------------------------'.PHP_EOL; $expected .= ' TestStandard.Deprecated.WithLongReplacement *'.PHP_EOL; $expected .= ' TestStandard.Deprecated.WithoutReplacement *'.PHP_EOL; @@ -198,7 +198,8 @@ public function testExplainWithDeprecatedSniffs() $expected .= ' TestStandard.SetProperty.AllowedViaMagicMethod'.PHP_EOL; $expected .= ' TestStandard.SetProperty.AllowedViaStdClass'.PHP_EOL; $expected .= ' TestStandard.SetProperty.NotAllowedViaAttribute'.PHP_EOL; - $expected .= ' TestStandard.SetProperty.PropertyTypeHandling'.PHP_EOL.PHP_EOL; + $expected .= ' TestStandard.SetProperty.PropertyTypeHandling'.PHP_EOL; + $expected .= ' TestStandard.ValidSniffs.RegisterEmptyArray'.PHP_EOL.PHP_EOL; $expected .= '* Sniffs marked with an asterix are deprecated.'.PHP_EOL; diff --git a/tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffs/RegisterNoArraySniff.php b/tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffs/RegisterNoArraySniff.php new file mode 100644 index 0000000000..a77ac24fa5 --- /dev/null +++ b/tests/Core/Ruleset/Fixtures/TestStandard/Sniffs/InvalidSniffs/RegisterNoArraySniff.php @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/tests/Core/Ruleset/PopulateTokenListenersTest.php b/tests/Core/Ruleset/PopulateTokenListenersTest.php new file mode 100644 index 0000000000..713515b502 --- /dev/null +++ b/tests/Core/Ruleset/PopulateTokenListenersTest.php @@ -0,0 +1,451 @@ + + * @copyright 2024 PHPCSStandards and contributors + * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence + */ + +namespace PHP_CodeSniffer\Tests\Core\Ruleset; + +use PHP_CodeSniffer\Ruleset; +use PHP_CodeSniffer\Tests\ConfigDouble; +use PHP_CodeSniffer\Tests\Core\Ruleset\AbstractRulesetTestCase; +use PHP_CodeSniffer\Util\Tokens; +use ReflectionObject; +use ReflectionProperty; + +/** + * Test the Ruleset::populateTokenListeners() method. + * + * @covers \PHP_CodeSniffer\Ruleset::populateTokenListeners + */ +final class PopulateTokenListenersTest extends AbstractRulesetTestCase +{ + + /** + * The Ruleset object. + * + * @var \PHP_CodeSniffer\Ruleset + */ + private static $ruleset; + + + /** + * Initialize the config and ruleset objects for this test only once (but do allow recording code coverage). + * + * @before + * + * @return void + */ + protected function initializeConfigAndRuleset() + { + if (isset(self::$ruleset) === false) { + // Set up the ruleset. + $standard = __DIR__.'/PopulateTokenListenersTest.xml'; + $config = new ConfigDouble(["--standard=$standard"]); + self::$ruleset = new Ruleset($config); + } + + }//end initializeConfigAndRuleset() + + + /** + * Test an exception is thrown when the register() method of a sniff doesn't return an array. + * + * @return void + */ + public function testSniffWhereRegisterDoesNotReturnAnArrayThrowsException() + { + $standard = __DIR__.'/PopulateTokenListenersRegisterNoArrayTest.xml'; + $config = new ConfigDouble(["--standard=$standard"]); + + $sniffClass = 'Fixtures\\TestStandard\\Sniffs\\InvalidSniffs\\RegisterNoArraySniff'; + $message = "Sniff $sniffClass register() method must return an array"; + $this->expectRuntimeExceptionMessage($message); + + new Ruleset($config); + + }//end testSniffWhereRegisterDoesNotReturnAnArrayThrowsException() + + + /** + * Test that a sniff not registering any tokens is not listed as a listener. + * + * @return void + */ + public function testSniffWithRegisterMethodReturningEmptyArrayIsSilentlyIgnored() + { + $target = 'Fixtures\\TestStandard\\Sniffs\\ValidSniffs\\RegisterEmptyArraySniff'; + + foreach (self::$ruleset->tokenListeners as $token => $listeners) { + $this->assertTrue(is_array($listeners), 'No listeners registered for token'.Tokens::tokenName($token)); + $this->assertArrayNotHasKey( + $target, + $listeners, + sprintf('Found the %s sniff registered for token %s', $target, Tokens::tokenName($token)) + ); + } + + }//end testSniffWithRegisterMethodReturningEmptyArrayIsSilentlyIgnored() + + + /** + * Tests that sniffs registering tokens, will end up listening to these tokens. + * + * @param string $sniffClass The FQN for the sniff class to check. + * @param int $expectedCount Expected number of tokens to which the sniff should be listening. + * + * @dataProvider dataSniffListensToTokenss + * + * @return void + */ + public function testRegistersSniffsToListenToTokens($sniffClass, $expectedCount) + { + $counter = 0; + + foreach (self::$ruleset->tokenListeners as $listeners) { + if (isset($listeners[$sniffClass]) === true) { + ++$counter; + } + } + + $this->assertSame($expectedCount, $counter); + + }//end testRegistersSniffsToListenToTokens() + + + /** + * Data provider. + * + * @see testSniffListensToTokens() + * + * @return array> + */ + public static function dataSniffListensToTokenss() + { + return [ + 'Generic.Files.EndFileNewline' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\Files\\EndFileNewlineSniff', + 'expectedCount' => 2, + ], + 'Generic.NamingConventions.UpperCaseConstantName' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff', + 'expectedCount' => 2, + ], + 'PSR1.Files.SideEffects' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Files\\SideEffectsSniff', + 'expectedCount' => 1, + ], + 'PSR12.ControlStructures.BooleanOperatorPlacement' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\PSR12\\Sniffs\\ControlStructures\\BooleanOperatorPlacementSniff', + 'expectedCount' => 5, + ], + 'Squiz.ControlStructures.ForEachLoopDeclaration' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Squiz\\Sniffs\\ControlStructures\\ForEachLoopDeclarationSniff', + 'expectedCount' => 1, + ], + 'TestStandard.Deprecated.WithReplacement' => [ + 'sniffClass' => 'Fixtures\\TestStandard\\Sniffs\\Deprecated\\WithReplacementSniff', + 'expectedCount' => 1, + ], + 'TestStandard.ValidSniffs.RegisterEmptyArray' => [ + 'sniffClass' => 'Fixtures\\TestStandard\\Sniffs\\ValidSniffs\\RegisterEmptyArraySniff', + 'expectedCount' => 0, + ], + ]; + + }//end dataSniffListensToTokenss() + + + /** + * Test that deprecated sniffs get recognized and added to the $deprecatedSniffs list. + * + * @return void + */ + public function testRegistersWhenADeprecatedSniffIsLoaded() + { + $property = new ReflectionProperty(self::$ruleset, 'deprecatedSniffs'); + $property->setAccessible(true); + $actualValue = $property->getValue(self::$ruleset); + $property->setAccessible(false); + + // Only verify there is one deprecated sniff registered. + // There are other tests which test the deprecated sniff handling in more detail. + $this->assertTrue(is_array($actualValue)); + $this->assertCount(1, $actualValue); + + }//end testRegistersWhenADeprecatedSniffIsLoaded() + + + /** + * Verify that the setting of properties on a sniff was not triggered when there are no properties being set. + * + * @return void + */ + public function testDoesntTriggerPropertySettingForNoProperties() + { + $sniffClass = 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff'; + + // Verify that our target sniff has been registered. + $this->assertArrayHasKey($sniffClass, self::$ruleset->sniffs, "Sniff class $sniffClass not listed in registered sniffs"); + + $sniffObject = self::$ruleset->sniffs[$sniffClass]; + $reflection = new ReflectionObject($sniffObject); + + // Just making sure there are no properties on the sniff object (which doesn't have declared properties). + $this->assertSame([], $reflection->getProperties(), "Unexpected properties found on sniff class $sniffClass"); + + }//end testDoesntTriggerPropertySettingForNoProperties() + + + /** + * Verify that the setting of properties on a sniff was triggered. + * + * @param string $sniffClass The FQN for the sniff class on which the property should be set. + * @param string $propertyName The property name. + * @param string $expected The expected property value. + * + * @dataProvider dataTriggersPropertySettingWhenPropertiesProvided + * + * @return void + */ + public function testTriggersPropertySettingWhenPropertiesProvided($sniffClass, $propertyName, $expected) + { + // Verify that our target sniff has been registered. + $this->assertArrayHasKey($sniffClass, self::$ruleset->sniffs, "Sniff class $sniffClass not listed in registered sniffs"); + + $sniffObject = self::$ruleset->sniffs[$sniffClass]; + + // Verify the property has been set. + $this->assertSame($expected, $sniffObject->$propertyName, "Property on sniff class $sniffClass set to unexpected value"); + + }//end testTriggersPropertySettingWhenPropertiesProvided() + + + /** + * Data provider. + * + * @see testTriggersPropertySettingWhenPropertiesProvided() + * + * @return array> + */ + public static function dataTriggersPropertySettingWhenPropertiesProvided() + { + return [ + 'Sniff with single property being set' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\PSR12\\Sniffs\\ControlStructures\\BooleanOperatorPlacementSniff', + 'propertyName' => 'allowOnly', + 'expected' => 'first', + ], + 'Sniff with multiple properties being set - first property' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Squiz\\Sniffs\\ControlStructures\\ForEachLoopDeclarationSniff', + 'propertyName' => 'requiredSpacesAfterOpen', + 'expected' => '3', + ], + 'Sniff with multiple properties being set - second property' => [ + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Squiz\\Sniffs\\ControlStructures\\ForEachLoopDeclarationSniff', + 'propertyName' => 'requiredSpacesBeforeClose', + 'expected' => '8', + ], + ]; + + }//end dataTriggersPropertySettingWhenPropertiesProvided() + + + /** + * Verifies that the "class" and "source" indexes get set. + * + * @return void + */ + public function testSetsClassAndSourceIndexes() + { + foreach (self::$ruleset->tokenListeners as $token => $listeners) { + $this->assertTrue(is_array($listeners), 'No listeners registered for token'.Tokens::tokenName($token)); + + foreach ($listeners as $className => $details) { + $this->assertArrayHasKey( + 'class', + $details, + sprintf('"tokenizers" key missing for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + + $this->assertSame( + $className, + $details['class'], + sprintf('Unexpected value for "class" key for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + + $this->assertArrayHasKey( + 'source', + $details, + sprintf('"source" key missing for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + + $this->assertTrue( + is_string($details['source']), + sprintf('Value for "source" key is not a string for token %s', Tokens::tokenName($token)) + ); + + $expected = '.'.substr($className, (strrpos($className, '\\') + 1), -5); + + $this->assertStringEndsWith( + $expected, + $details['source'], + sprintf('Unexpected value for "source" key for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + }//end foreach + }//end foreach + + }//end testSetsClassAndSourceIndexes() + + + /** + * Verifies that by default no explicit include patterns are registered for sniffs. + * + * @return void + */ + public function testSetsIncludePatternsToEmptyArrayByDefault() + { + $exclude = 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff'; + + foreach (self::$ruleset->tokenListeners as $token => $listeners) { + $this->assertTrue(is_array($listeners), 'No listeners registered for token'.Tokens::tokenName($token)); + + foreach ($listeners as $className => $details) { + if ($className === $exclude) { + // Skip this one as it is the one sniff for which things will be different. + continue; + } + + $this->assertArrayHasKey( + 'include', + $details, + sprintf('"include" key missing for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + + $this->assertSame( + [], + $details['include'], + sprintf('Unexpected value for "include" key for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + } + }//end foreach + + }//end testSetsIncludePatternsToEmptyArrayByDefault() + + + /** + * Verifies that by default no explicit ignore patterns are registered for sniffs. + * + * @return void + */ + public function testSetsIgnorePatternsToEmptyArrayByDefault() + { + $exclude = 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Files\\SideEffectsSniff'; + + foreach (self::$ruleset->tokenListeners as $token => $listeners) { + $this->assertTrue(is_array($listeners), 'No listeners registered for token'.Tokens::tokenName($token)); + + foreach ($listeners as $className => $details) { + if ($className === $exclude) { + // Skip this one as it is the one sniff for which things will be different. + continue; + } + + $this->assertArrayHasKey( + 'ignore', + $details, + sprintf('"ignore" key missing for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + + $this->assertSame( + [], + $details['ignore'], + sprintf('Unexpected value for "ignore" key for sniff class %s for token %s', $className, Tokens::tokenName($token)) + ); + } + }//end foreach + + }//end testSetsIgnorePatternsToEmptyArrayByDefault() + + + /** + * Tests that if there are <[include|exclude]-pattern> directives set on a sniff, these are set for the relevant listeners. + * + * Includes verification that the transformation of "regex"-like patterns is handled correctly. + * + * @param int|string $token A token constant on which the sniff should be registered. + * @param string $sniffClass The FQN for the sniff class on which the patterns should be registered. + * @param string $patternType The type of patterns expected to be registered for the sniff. + * + * @dataProvider dataSetsIncludeAndIgnorePatterns + * + * @return void + */ + public function testSetsIncludeAndIgnorePatterns($token, $sniffClass, $patternType) + { + $expected = [ + '/no-transformation/', + '/simple.*transformation/.*', + '/escaped\\,comma/becomes/comma/to/allow/commas/in/filenames.css', + '/pat?tern(is|regex)\\.php$', + ]; + + $this->assertArrayHasKey( + $token, + self::$ruleset->tokenListeners, + sprintf('The token constant %s is not registered to the listeners array', Tokens::tokenName($token)) + ); + $this->assertArrayHasKey( + $sniffClass, + self::$ruleset->tokenListeners[$token], + sprintf('The sniff class %s is not registered for token %s', $sniffClass, Tokens::tokenName($token)) + ); + $this->assertArrayHasKey( + $patternType, + self::$ruleset->tokenListeners[$token][$sniffClass], + sprintf('"%s" key missing for sniff class %s for token %s', $patternType, $sniffClass, Tokens::tokenName($token)) + ); + + $this->assertSame( + $expected, + self::$ruleset->tokenListeners[$token][$sniffClass][$patternType], + sprintf('Unexpected value for "%s" key for sniff class %s for token %s', $patternType, $sniffClass, Tokens::tokenName($token)) + ); + + }//end testSetsIncludeAndIgnorePatterns() + + + /** + * Data provider. + * + * @see testSetsIncludeAndIgnorePatterns() + * + * @return array> + */ + public static function dataSetsIncludeAndIgnorePatterns() + { + return [ + 'Sniff with s in the ruleset - first token' => [ + 'token' => T_STRING, + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff', + 'patternType' => 'include', + ], + 'Sniff with s in the ruleset - second token' => [ + 'token' => T_CONST, + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\NamingConventions\\UpperCaseConstantNameSniff', + 'patternType' => 'include', + ], + 'Sniff with s in the ruleset' => [ + 'token' => T_OPEN_TAG, + 'sniffClass' => 'PHP_CodeSniffer\\Standards\\PSR1\\Sniffs\\Files\\SideEffectsSniff', + 'patternType' => 'ignore', + ], + ]; + + }//end dataSetsIncludeAndIgnorePatterns() + + +}//end class diff --git a/tests/Core/Ruleset/PopulateTokenListenersTest.xml b/tests/Core/Ruleset/PopulateTokenListenersTest.xml new file mode 100644 index 0000000000..f61ab500d0 --- /dev/null +++ b/tests/Core/Ruleset/PopulateTokenListenersTest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /no-transformation/ + /simple*transformation/* + /escaped\\,comma/becomes/comma/to/allow/commas/in/filenames.css + /pat?tern(is|regex)\.php$ + + + + + /no-transformation/ + /simple*transformation/* + /escaped\\,comma/becomes/comma/to/allow/commas/in/filenames.css + /pat?tern(is|regex)\.php$ + + + diff --git a/tests/Core/Ruleset/ProcessRulesetAutoExpandSniffsDirectoryTest.xml b/tests/Core/Ruleset/ProcessRulesetAutoExpandSniffsDirectoryTest.xml index 6968808664..579b9485d2 100644 --- a/tests/Core/Ruleset/ProcessRulesetAutoExpandSniffsDirectoryTest.xml +++ b/tests/Core/Ruleset/ProcessRulesetAutoExpandSniffsDirectoryTest.xml @@ -3,6 +3,8 @@ - + + + diff --git a/tests/Core/Ruleset/ProcessRulesetTest.php b/tests/Core/Ruleset/ProcessRulesetTest.php index d53889a88c..c18173b476 100644 --- a/tests/Core/Ruleset/ProcessRulesetTest.php +++ b/tests/Core/Ruleset/ProcessRulesetTest.php @@ -72,6 +72,7 @@ public function testAutoExpandSniffsDirectory() "$std.SetProperty.AllowedViaStdClass" => "$sniffDir\SetProperty\AllowedViaStdClassSniff", "$std.SetProperty.NotAllowedViaAttribute" => "$sniffDir\SetProperty\NotAllowedViaAttributeSniff", "$std.SetProperty.PropertyTypeHandling" => "$sniffDir\SetProperty\PropertyTypeHandlingSniff", + "$std.ValidSniffs.RegisterEmptyArray" => "$sniffDir\ValidSniffs\RegisterEmptyArraySniff", ]; // Sort the value to make the tests stable as different OSes will read directories diff --git a/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml b/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml index 38c7e02221..802bd3c0a9 100644 --- a/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml +++ b/tests/Core/Ruleset/ShowSniffDeprecationsTest.xml @@ -5,6 +5,7 @@ +