diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch1Test.xml b/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch1Test.xml
new file mode 100644
index 0000000000..b42437419a
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch1Test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch2Test.xml b/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch2Test.xml
new file mode 100644
index 0000000000..180b99ba23
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceCaseMismatch2Test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceHomePathFailTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathFailTest.xml
new file mode 100644
index 0000000000..803c848de6
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathFailTest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.php b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.php
new file mode 100644
index 0000000000..a644af08c7
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.php
@@ -0,0 +1,120 @@
+
+ * @copyright 2025 PHPCSStandards and contributors
+ * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Ruleset;
+
+use PHP_CodeSniffer\Ruleset;
+use PHP_CodeSniffer\Tests\ConfigDouble;
+use PHP_CodeSniffer\Tests\Core\Ruleset\AbstractRulesetTestCase;
+
+/**
+ * Test home path handling in the Ruleset::expandRulesetReference() method.
+ *
+ * @covers \PHP_CodeSniffer\Ruleset::expandRulesetReference
+ */
+final class ExpandRulesetReferenceHomePathTest extends AbstractRulesetTestCase
+{
+
+ /**
+ * Original value of the user's home path environment variable.
+ *
+ * @var string|false Path or false is the `HOME` environment variable is not available.
+ */
+ private static $homepath = false;
+
+
+ /**
+ * Store the user's home path.
+ *
+ * @beforeClass
+ *
+ * @return void
+ */
+ protected function storeHomePath()
+ {
+ $this->homepath = getenv('HOME');
+
+ }//end storeHomePath()
+
+
+ /**
+ * Restore the user's home path environment variable in case the test changed it or created it.
+ *
+ * @afterClass
+ *
+ * @return void
+ */
+ protected function restoreHomePath()
+ {
+ if (is_string($this->homepath) === true) {
+ putenv('HOME='.$this->homepath);
+ } else {
+ // Remove the environment variable as it didn't exist before.
+ putenv('HOME');
+ }
+
+ }//end restoreHomePath()
+
+
+ /**
+ * Set the home path to an alternative location.
+ *
+ * @before
+ *
+ * @return void
+ */
+ protected function setHomePath()
+ {
+ $fakeHomePath = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'FakeHomePath';
+ putenv("HOME=$fakeHomePath");
+
+ }//end setHomePath()
+
+
+ /**
+ * Verify that a sniff reference with the magic "home path" placeholder gets expanded correctly
+ * and finds sniffs if the path exists underneath the "home path".
+ *
+ * @return void
+ */
+ public function testHomePathRefGetsExpandedAndFindsSniff()
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/ExpandRulesetReferenceHomePathTest.xml';
+ $config = new ConfigDouble(["--standard=$standard"]);
+ $ruleset = new Ruleset($config);
+
+ $expected = ['MyStandard.Category.Valid' => 'FakeHomePath\\MyStandard\\Sniffs\\Category\\ValidSniff'];
+
+ $this->assertSame($expected, $ruleset->sniffCodes);
+
+ }//end testHomePathRefGetsExpandedAndFindsSniff()
+
+
+ /**
+ * Verify that a sniff reference with the magic "home path" placeholder gets expanded correctly
+ * and still fails to find sniffs if the path doesn't exists underneath the "home path".
+ *
+ * @return void
+ */
+ public function testHomePathRefGetsExpandedAndThrowsExceptionWhenPathIsInvalid()
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/ExpandRulesetReferenceHomePathFailTest.xml';
+ $config = new ConfigDouble(["--standard=$standard"]);
+
+ $exceptionMessage = 'Referenced sniff "~/src/MyStandard/Sniffs/DoesntExist/" does not exist';
+ $this->expectRuntimeExceptionMessage($exceptionMessage);
+
+ new Ruleset($config);
+
+ }//end testHomePathRefGetsExpandedAndThrowsExceptionWhenPathIsInvalid()
+
+
+}//end class
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.xml
new file mode 100644
index 0000000000..8d39a26935
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceHomePathTest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInternalIgnoreTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInternalIgnoreTest.xml
new file mode 100644
index 0000000000..9398e402b7
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInternalIgnoreTest.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInternalStandardTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInternalStandardTest.xml
new file mode 100644
index 0000000000..0493e253c6
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInternalStandardTest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInternalTest.php b/tests/Core/Ruleset/ExpandRulesetReferenceInternalTest.php
new file mode 100644
index 0000000000..edac7490d0
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInternalTest.php
@@ -0,0 +1,66 @@
+
+ * @copyright 2025 PHPCSStandards and contributors
+ * @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\Ruleset;
+
+use PHP_CodeSniffer\Ruleset;
+use PHP_CodeSniffer\Tests\ConfigDouble;
+use PHP_CodeSniffer\Tests\Core\Ruleset\AbstractRulesetTestCase;
+
+/**
+ * Test handling of "internal" references in the Ruleset::expandRulesetReference() method.
+ *
+ * @covers \PHP_CodeSniffer\Ruleset::expandRulesetReference
+ */
+final class ExpandRulesetReferenceInternalTest extends AbstractRulesetTestCase
+{
+
+
+ /**
+ * Verify that a ruleset reference starting with "Internal." (including the dot) doesn't cause any sniffs to be registered.
+ *
+ * @return void
+ */
+ public function testInternalRefDoesNotGetExpanded()
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/ExpandRulesetReferenceInternalIgnoreTest.xml';
+ $config = new ConfigDouble(["--standard=$standard"]);
+ $ruleset = new Ruleset($config);
+
+ $expected = ['Generic.PHP.BacktickOperator' => 'PHP_CodeSniffer\\Standards\\Generic\\Sniffs\\PHP\\BacktickOperatorSniff'];
+
+ $this->assertSame($expected, $ruleset->sniffCodes);
+
+ }//end testInternalRefDoesNotGetExpanded()
+
+
+ /**
+ * While definitely not recommended, including a standard named "Internal", _does_ allow for sniffs to be registered.
+ *
+ * Note: customizations (exclusions/property setting etc) for individual sniffs may not always be handled correctly,
+ * which is why naming a standard "Internal" is definitely not recommended.
+ *
+ * @return void
+ */
+ public function testInternalStandardDoesGetExpanded()
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/ExpandRulesetReferenceInternalStandardTest.xml';
+ $config = new ConfigDouble(["--standard=$standard"]);
+ $ruleset = new Ruleset($config);
+
+ $expected = ['Internal.Valid.Valid' => 'Fixtures\\Internal\\Sniffs\\Valid\\ValidSniff'];
+
+ $this->assertSame($expected, $ruleset->sniffCodes);
+
+ }//end testInternalStandardDoesGetExpanded()
+
+
+}//end class
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode1Test.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode1Test.xml
new file mode 100644
index 0000000000..f864f35136
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode1Test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode2Test.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode2Test.xml
new file mode 100644
index 0000000000..49814db7b5
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode2Test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode3Test.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode3Test.xml
new file mode 100644
index 0000000000..439fe9a672
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidErrorCode3Test.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceInvalidHomePathRefTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidHomePathRefTest.xml
new file mode 100644
index 0000000000..801ead6dd7
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceInvalidHomePathRefTest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceMissingFileTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceMissingFileTest.xml
new file mode 100644
index 0000000000..c7d95e431f
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceMissingFileTest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceTest.php b/tests/Core/Ruleset/ExpandRulesetReferenceTest.php
new file mode 100644
index 0000000000..adf1d3e32a
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceTest.php
@@ -0,0 +1,133 @@
+
+ * @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;
+
+/**
+ * Test various aspects of the Ruleset::expandRulesetReference() method not covered by other tests.
+ *
+ * @covers \PHP_CodeSniffer\Ruleset::expandRulesetReference
+ */
+final class ExpandRulesetReferenceTest extends AbstractRulesetTestCase
+{
+
+
+ /**
+ * Test handling of path references relative to the originally included ruleset.
+ *
+ * @return void
+ */
+ public function testRulesetRelativePathReferences()
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/ExpandRulesetReferenceTest.xml';
+ $config = new ConfigDouble(["--standard=$standard"]);
+ $ruleset = new Ruleset($config);
+
+ $expected = [
+ 'ExternalA.CheckSomething.Valid' => 'Fixtures\\ExternalA\\Sniffs\\CheckSomething\\ValidSniff',
+ 'TestStandard.ValidSniffs.RegisterEmptyArray' => 'Fixtures\\TestStandard\\Sniffs\\ValidSniffs\\RegisterEmptyArraySniff',
+ 'ExternalB.CheckMore.Valid' => 'Fixtures\\ExternalB\\Sniffs\\CheckMore\\ValidSniff',
+ ];
+
+ $this->assertSame($expected, $ruleset->sniffCodes);
+
+ }//end testRulesetRelativePathReferences()
+
+
+ /**
+ * Test that an exception is thrown if a ruleset contains an unresolvable reference.
+ *
+ * @param string $standard The standard to use for the test.
+ * @param string $replacement The reference which will be used in the exception message.
+ *
+ * @dataProvider dataUnresolvableReferenceThrowsException
+ *
+ * @return void
+ */
+ public function testUnresolvableReferenceThrowsException($standard, $replacement)
+ {
+ // Set up the ruleset.
+ $standard = __DIR__.'/'.$standard;
+ $config = new ConfigDouble(["--standard=$standard"]);
+
+ $exceptionMessage = 'Referenced sniff "%s" does not exist';
+ $this->expectRuntimeExceptionMessage(sprintf($exceptionMessage, $replacement));
+
+ new Ruleset($config);
+
+ }//end testUnresolvableReferenceThrowsException()
+
+
+ /**
+ * Data provider.
+ *
+ * @see testUnresolvableReferenceThrowsException()
+ *
+ * @return array>
+ */
+ public static function dataUnresolvableReferenceThrowsException()
+ {
+ $data = [
+ 'Referencing a non-existent XML file' => [
+ 'standard' => 'ExpandRulesetReferenceMissingFileTest.xml',
+ 'replacement' => './MissingFile.xml',
+ ],
+ 'Referencing an invalid directory starting with "~"' => [
+ 'standard' => 'ExpandRulesetReferenceInvalidHomePathRefTest.xml',
+ 'replacement' => '~/src/Standards/Squiz/Sniffs/Files/',
+ ],
+ 'Referencing an unknown standard' => [
+ 'standard' => 'ExpandRulesetReferenceUnknownStandardTest.xml',
+ 'replacement' => 'UnknownStandard',
+ ],
+ 'Referencing a non-existent category in a known standard' => [
+ 'standard' => 'ExpandRulesetReferenceUnknownCategoryTest.xml',
+ 'replacement' => 'TestStandard.UnknownCategory',
+ ],
+ 'Referencing a non-existent sniff in a known standard' => [
+ 'standard' => 'ExpandRulesetReferenceUnknownSniffTest.xml',
+ 'replacement' => 'TestStandard.InvalidSniffs.UnknownRule',
+ ],
+ 'Referencing an invalid error code - no standard name' => [
+ 'standard' => 'ExpandRulesetReferenceInvalidErrorCode1Test.xml',
+ 'replacement' => '.Invalid.Undetermined.Found',
+ ],
+ 'Referencing an invalid error code - no category name' => [
+ 'standard' => 'ExpandRulesetReferenceInvalidErrorCode2Test.xml',
+ 'replacement' => 'Standard..Undetermined.Found',
+ ],
+ 'Referencing an invalid error code - no sniff name' => [
+ 'standard' => 'ExpandRulesetReferenceInvalidErrorCode3Test.xml',
+ 'replacement' => 'Standard.Invalid..Found',
+ ],
+ ];
+
+ // Add tests which are only relevant for case-sensitive OSes.
+ if (stripos(PHP_OS, 'WIN') === false) {
+ $data['Referencing an existing sniff, but there is a case mismatch (OS-dependent) [1]'] = [
+ 'standard' => 'ExpandRulesetReferenceCaseMismatch1Test.xml',
+ 'replacement' => 'psr12.functions.nullabletypedeclaration',
+ ];
+ $data['Referencing an existing sniff, but there is a case mismatch (OS-dependent) [2]'] = [
+ 'standard' => 'ExpandRulesetReferenceCaseMismatch2Test.xml',
+ 'replacement' => 'PSR12.Functions.ReturntypeDeclaration',
+ ];
+ }
+
+ return $data;
+
+ }//end dataUnresolvableReferenceThrowsException()
+
+
+}//end class
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceTest.xml
new file mode 100644
index 0000000000..aaa23759ee
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceTest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceUnknownCategoryTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownCategoryTest.xml
new file mode 100644
index 0000000000..26f8c8491b
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownCategoryTest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceUnknownSniffTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownSniffTest.xml
new file mode 100644
index 0000000000..8b9b3ad197
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownSniffTest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/ExpandRulesetReferenceUnknownStandardTest.xml b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownStandardTest.xml
new file mode 100644
index 0000000000..8563f860dc
--- /dev/null
+++ b/tests/Core/Ruleset/ExpandRulesetReferenceUnknownStandardTest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/Core/Ruleset/Fixtures/ExternalA/Sniffs/CheckSomething/ValidSniff.php b/tests/Core/Ruleset/Fixtures/ExternalA/Sniffs/CheckSomething/ValidSniff.php
new file mode 100644
index 0000000000..51061e3b5e
--- /dev/null
+++ b/tests/Core/Ruleset/Fixtures/ExternalA/Sniffs/CheckSomething/ValidSniff.php
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/tests/Core/Ruleset/Fixtures/ExternalB/Sniffs/CheckMore/ValidSniff.php b/tests/Core/Ruleset/Fixtures/ExternalB/Sniffs/CheckMore/ValidSniff.php
new file mode 100644
index 0000000000..1584f70504
--- /dev/null
+++ b/tests/Core/Ruleset/Fixtures/ExternalB/Sniffs/CheckMore/ValidSniff.php
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/tests/Core/Ruleset/Fixtures/FakeHomePath/src/MyStandard/Sniffs/Category/ValidSniff.php b/tests/Core/Ruleset/Fixtures/FakeHomePath/src/MyStandard/Sniffs/Category/ValidSniff.php
new file mode 100644
index 0000000000..9346df9eb1
--- /dev/null
+++ b/tests/Core/Ruleset/Fixtures/FakeHomePath/src/MyStandard/Sniffs/Category/ValidSniff.php
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/tests/Core/Ruleset/Fixtures/Internal/Sniffs/Valid/ValidSniff.php b/tests/Core/Ruleset/Fixtures/Internal/Sniffs/Valid/ValidSniff.php
new file mode 100644
index 0000000000..e7a04f495a
--- /dev/null
+++ b/tests/Core/Ruleset/Fixtures/Internal/Sniffs/Valid/ValidSniff.php
@@ -0,0 +1,25 @@
+
+
+
+
+
+