diff --git a/AGENTS.md b/AGENTS.md
index 2cc65d4..306faa7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -128,3 +128,75 @@ tests/Sniffs/Generic.Files/LineLength/
- Second line: Comment explaining test (e.g., `// Test: lineLimit=120`)
- Use tabs for indentation
- Include code that exercises the specific parameter being tested
+
+## Issue Testing
+
+Issue tests validate that specific GitHub issues are properly handled. These tests are located in `tests/Issue/{number}/`.
+
+### Creating Issue Tests
+
+1. **Create issue directory**: `tests/Issue/{number}/`
+2. **Create test files**:
+ - `good.php` - Code that should pass without errors (regression test)
+ - `good.ruleset.xml` - Ruleset with relevant sniffs
+ - Optionally: `bad.php` / `bad.ruleset.xml` for cases that should produce errors
+3. **Regenerate snapshots**: `php bin/snapshots --issues` or `php bin/snapshots --issue={number}`
+4. **Run tests**: `make tests`
+
+**Example issue test structure:**
+
+```
+tests/Issue/19/
+├── good.php # Code demonstrating the fix works
+├── good.ruleset.xml # Ruleset with relevant sniffs
+└── good.snapshot.json # Expected output (0 errors for good.php)
+```
+
+**Example good.php:**
+
+```php
+
+
+
+
+
+
+
+```
+
+**Commands for issue testing:**
+
+```bash
+# Generate all issue snapshots
+php bin/snapshots --issues
+
+# Generate specific issue snapshot
+php bin/snapshots --issue=19
+
+# Test code manually against an issue ruleset
+vendor/bin/phpcs --standard=tests/Issue/19/good.ruleset.xml tests/Issue/19/good.php
+```
diff --git a/bin/snapshots b/bin/snapshots
index a1825c9..f1a553f 100755
--- a/bin/snapshots
+++ b/bin/snapshots
@@ -9,6 +9,8 @@
* php bin/snapshots --set=8.0 # Generate only set 8.0
* php bin/snapshots --sniffs # Generate only sniff snapshots
* php bin/snapshots --sniff=Generic.Arrays.DisallowLongArraySyntax
+ * php bin/snapshots --issues # Generate only issue snapshots
+ * php bin/snapshots --issue=19 # Generate only specific issue snapshot
*/
$rootDir = dirname(__DIR__);
@@ -23,8 +25,10 @@ use Tests\Toolkit\Codesniffer;
// Parse CLI arguments
$filterSet = null;
$filterSniff = null;
+$filterIssue = null;
$sniffsOnly = false;
$setsOnly = false;
+$issuesOnly = false;
foreach ($argv as $arg) {
if (str_starts_with($arg, '--set=')) {
@@ -37,6 +41,11 @@ foreach ($argv as $arg) {
$sniffsOnly = true;
}
+ if (str_starts_with($arg, '--issue=')) {
+ $filterIssue = substr($arg, 8);
+ $issuesOnly = true;
+ }
+
if ($arg === '--sniffs') {
$sniffsOnly = true;
}
@@ -44,13 +53,17 @@ foreach ($argv as $arg) {
if ($arg === '--sets') {
$setsOnly = true;
}
+
+ if ($arg === '--issues') {
+ $issuesOnly = true;
+ }
}
// ============================================================================
// Part 1: Generate Set Snapshots
// ============================================================================
-if (!$sniffsOnly) {
+if (!$sniffsOnly && !$issuesOnly) {
$setsDir = $rootDir . '/tests/Sets';
echo "=== Generating Set Snapshots ===\n\n";
@@ -126,7 +139,7 @@ if (!$sniffsOnly) {
// Part 2: Generate Sniff Snapshots
// ============================================================================
-if (!$setsOnly) {
+if (!$setsOnly && !$issuesOnly) {
$sniffsDir = $rootDir . '/tests/Sniffs';
echo "\n=== Generating Sniff Snapshots ===\n\n";
@@ -196,4 +209,71 @@ if (!$setsOnly) {
echo "\nSniff snapshots: {$generated} generated.\n";
}
+// ============================================================================
+// Part 3: Generate Issue Snapshots
+// ============================================================================
+
+if (!$setsOnly && !$sniffsOnly) {
+ $issuesDir = $rootDir . '/tests/Issue';
+
+ echo "\n=== Generating Issue Snapshots ===\n\n";
+
+ $generated = 0;
+
+ if (is_dir($issuesDir)) {
+ // Find all issue directories (e.g., 19)
+ foreach (Finder::findDirectories('*')->in($issuesDir) as $issueDir) {
+ $issueName = $issueDir->getBasename();
+
+ if ($filterIssue !== null && $filterIssue !== $issueName) {
+ continue;
+ }
+
+ // Find all PHP test files in the issue directory
+ foreach (Finder::findFiles('*.php')->in($issueDir->getPathname()) as $phpFile) {
+ $baseName = $phpFile->getBasename('.php');
+ $snapshotPath = $issueDir->getPathname() . '/' . $baseName . '.snapshot.json';
+ $rulesetPath = $issueDir->getPathname() . '/' . $baseName . '.ruleset.xml';
+
+ if (!file_exists($rulesetPath)) {
+ echo "Warning: Ruleset not found: {$rulesetPath}\n";
+
+ continue;
+ }
+
+ $process = new Process([
+ 'vendor/bin/phpcs',
+ '--standard=' . $rulesetPath,
+ '--report=json',
+ '-q',
+ $phpFile->getPathname(),
+ ], $rootDir);
+ $process->run();
+
+ $data = json_decode($process->getOutput(), true);
+
+ if ($data === null) {
+ echo "Error: Failed to parse phpcs output for Issue/{$issueName}/{$baseName}.php\n";
+
+ continue;
+ }
+
+ $normalized = Codesniffer::normalize($data);
+ $json = json_encode($normalized, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
+
+ file_put_contents($snapshotPath, $json);
+
+ $errorCount = $normalized['totals']['errors'];
+ $warningCount = $normalized['totals']['warnings'];
+ echo "Generated: Issue/{$issueName}/{$baseName}.snapshot.json ({$errorCount} errors, {$warningCount} warnings)\n";
+ $generated++;
+ }
+ }
+ } else {
+ echo "No Issue directory found.\n";
+ }
+
+ echo "\nIssue snapshots: {$generated} generated.\n";
+}
+
echo "\nDone!\n";
diff --git a/tests/Issue/19/good.php b/tests/Issue/19/good.php
new file mode 100644
index 0000000..6ea19b4
--- /dev/null
+++ b/tests/Issue/19/good.php
@@ -0,0 +1,51 @@
+
+ */
+ public static function values(): array
+ {
+ return array_column(self::cases(), 'value');
+ }
+
+}
+
+trait EnumNames
+{
+
+ /**
+ * @return array
+ */
+ public static function names(): array
+ {
+ return array_column(self::cases(), 'name');
+ }
+
+}
+
+enum ImageTransform: int
+{
+
+ use EnumValues;
+ use EnumNames;
+
+ case Fit = 1;
+
+ case Exact = 2;
+
+ case Fill = 3;
+
+ case Stretch = 4;
+
+ case Shrink = 5;
+
+}
diff --git a/tests/Issue/19/good.ruleset.xml b/tests/Issue/19/good.ruleset.xml
new file mode 100644
index 0000000..af44b67
--- /dev/null
+++ b/tests/Issue/19/good.ruleset.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Issue/19/good.snapshot.json b/tests/Issue/19/good.snapshot.json
new file mode 100644
index 0000000..c963db6
--- /dev/null
+++ b/tests/Issue/19/good.snapshot.json
@@ -0,0 +1,13 @@
+{
+ "totals": {
+ "errors": 0,
+ "warnings": 0
+ },
+ "files": {
+ "good.php": {
+ "errors": 0,
+ "warnings": 0,
+ "messages": []
+ }
+ }
+}