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 @@
+