Skip to content

Files/FileList: adding the same file twice should not increment FileList::$numFiles #1150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Files/FileList.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ public function __construct(Config $config, Ruleset $ruleset)

foreach ($iterator as $file) {
$this->files[$file->getPathname()] = null;
$this->numFiles++;
}
} else {
$this->addFile($path);
}//end if
}//end foreach

reset($this->files);
$this->numFiles = count($this->files);

}//end __construct()

Expand Down Expand Up @@ -132,6 +132,11 @@ public function addFile($path, $file=null)
$iterator = new RecursiveIteratorIterator($filter);

foreach ($iterator as $path) {
if (array_key_exists($path, $this->files) === true) {
// The path has already been added.
continue;
}

$this->files[$path] = $file;
$this->numFiles++;
}
Expand Down
55 changes: 55 additions & 0 deletions tests/Core/Files/FileList/AbstractFileListTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/**
* Abstract testcase class for testing FileList methods.
*
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Files\FileList;

use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;
use PHPUnit\Framework\TestCase;

/**
* Base functionality and utilities for testing FileList methods.
*/
abstract class AbstractFileListTestCase extends TestCase
{

/**
* The Config object.
*
* @var \PHP_CodeSniffer\Config
*/
protected static $config;

/**
* The Ruleset object.
*
* @var \PHP_CodeSniffer\Ruleset
*/
protected static $ruleset;


/**
* Initialize the config and ruleset objects only once.
*
* @beforeClass
*
* @return void
*/
public static function initializeConfigAndRuleset()
{
// Wrapped in an `isset()` as the properties may have been set already (via a call to this method from a dataprovider).
if (isset(self::$ruleset) === false) {
self::$config = new ConfigDouble();
self::$config->filter = __DIR__.'/FilterDouble.php';
self::$ruleset = new Ruleset(self::$config);
}

}//end initializeConfigAndRuleset()


}//end class
138 changes: 138 additions & 0 deletions tests/Core/Files/FileList/AddFileTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\FileList::addFile method.
*
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Files\FileList;

use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Files\FileList;
use PHP_CodeSniffer\Ruleset;
use PHP_CodeSniffer\Tests\ConfigDouble;

/**
* Tests for the \PHP_CodeSniffer\Files\FileList::addFile method.
*
* @covers \PHP_CodeSniffer\Files\FileList::addFile
*/
final class AddFileTest extends AbstractFileListTestCase
{

/**
* The FileList object.
*
* @var \PHP_CodeSniffer\Files\FileList
*/
private $fileList;


/**
* Initialize the FileList object.
*
* @before
*
* @return void
*/
protected function initializeFileList()
{
self::$config->files = [];
$this->fileList = new FileList(self::$config, self::$ruleset);

}//end initializeFileList()


/**
* Test adding a file to the list.
*
* @param string $fileName The name of the file to add.
* @param object|null $fileObject An optional file object to add instead of creating a new one.
*
* @dataProvider dataAddFile
*
* @return void
*/
public function testAddFile($fileName, $fileObject=null)
{
$this->assertCount(0, $this->fileList);

$this->fileList->addFile($fileName, $fileObject);

$fileListArray = iterator_to_array($this->fileList);

$this->assertCount(1, $this->fileList, 'File count mismatch');
$this->assertArrayHasKey($fileName, $fileListArray, 'File not found in list');

if (isset($fileObject) === true) {
$this->assertSame($fileObject, $fileListArray[$fileName], 'File object mismatch');
} else {
$this->assertInstanceOf(
'PHP_CodeSniffer\Files\File',
$fileListArray[$fileName],
'File object not found in list'
);
}

}//end testAddFile()


/**
* Data provider for testAddFile.
*
* @return array<string, array<string, string|object>>
*/
public static function dataAddFile()
{
self::initializeConfigAndRuleset();

return [
'Regular file' => [
'fileName' => 'test1.php',
],
'STDIN' => [
'fileName' => 'STDIN',
],
'Regular file with file object' => [
'fileName' => 'test1.php',
'fileObject' => new File('test1.php', self::$ruleset, self::$config),
],
];

}//end dataAddFile()


/**
* Test that it is not possible to add the same file twice.
*
* @return void
*/
public function testAddFileShouldNotAddTheSameFileTwice()
{
$file1 = 'test1.php';
$file2 = 'test2.php';
$expectedFiles = [
$file1,
$file2,
];

// Add $file1 once.
$this->fileList->addFile($file1);
$this->assertCount(1, $this->fileList);
$this->assertSame([$file1], array_keys(iterator_to_array($this->fileList)));

// Try to add $file1 again. Should be ignored.
$this->fileList->addFile($file1);
$this->assertCount(1, $this->fileList);
$this->assertSame([$file1], array_keys(iterator_to_array($this->fileList)));

// Add $file2. Should be added.
$this->fileList->addFile($file2);
$this->assertCount(2, $this->fileList);
$this->assertSame($expectedFiles, array_keys(iterator_to_array($this->fileList)));

}//end testAddFileShouldNotAddTheSameFileTwice()


}//end class
122 changes: 122 additions & 0 deletions tests/Core/Files/FileList/ConstructTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php
/**
* Tests for the \PHP_CodeSniffer\Files\FileList::__construct method.
*
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Files\FileList;

use PHP_CodeSniffer\Files\FileList;

/**
* Tests for the \PHP_CodeSniffer\Files\FileList::__construct method.
*
* @covers \PHP_CodeSniffer\Files\FileList::__construct
*/
final class ConstructTest extends AbstractFileListTestCase
{


/**
* Test the __construct() method.
*
* @param array<string> $files List of file paths in the Config class.
* @param array<string> $expectedFiles List of expected file paths in the FileList.
*
* @dataProvider dataConstruct
*
* @return void
*/
public function testConstruct($files, $expectedFiles)
{
self::$config->files = $files;

$fileList = new FileList(self::$config, self::$ruleset);

$this->assertSame(self::$config, $fileList->config, 'Config object mismatch');
$this->assertSame(self::$ruleset, $fileList->ruleset, 'Ruleset object mismatch');

$this->assertCount(count($expectedFiles), $fileList, 'File count mismatch');

$i = 0;

// Sort the values to make the tests stable as different OSes will read directories
// in a different order and the order is not relevant for these tests. Just the values.
$fileListArray = iterator_to_array($fileList);
ksort($fileListArray);

foreach ($fileListArray as $filePath => $fileObject) {
$this->assertSame(
$expectedFiles[$i],
$filePath,
sprintf('File path mismatch: expected "%s", got "%s"', $expectedFiles[$i], $filePath)
);
$this->assertInstanceOf(
'PHP_CodeSniffer\Files\File',
$fileObject,
sprintf('File object for "%s" is not an instance of PHP_CodeSniffer\Files\File', $filePath)
);
$i++;
}

}//end testConstruct()


/**
* Data provider for testConstruct.
*
* @return array<string, array<string, array<string>>>
*/
public static function dataConstruct()
{
$fixturesDir = __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR;

return [
'No files' => [
'files' => [],
'expectedFiles' => [],
],
'Two files' => [
'files' => [
'file1.php',
'file2.php',
],
'expectedFiles' => [
'file1.php',
'file2.php',
],
],
'A directory' => [
'files' => [$fixturesDir],
'expectedFiles' => [
$fixturesDir.'file1.php',
$fixturesDir.'file2.php',
],
],
'Same file twice' => [
'files' => [
'file1.php',
'file1.php',
],
'expectedFiles' => [
'file1.php',
],
],
'File and then directory containing that file' => [
'files' => [
$fixturesDir.'file1.php',
$fixturesDir,
],
'expectedFiles' => [
$fixturesDir.'file1.php',
$fixturesDir.'file2.php',
],
],
];

}//end dataConstruct()


}//end class
31 changes: 31 additions & 0 deletions tests/Core/Files/FileList/FilterDouble.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Double of the filter class that will accept every file. Used in the FileList tests.
*
* @copyright 2025 PHPCSStandards Contributors
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
*/

namespace PHP_CodeSniffer\Tests\Core\Files\FileList;

use PHP_CodeSniffer\Filters\Filter;
use ReturnTypeWillChange;

final class FilterDouble extends Filter
{


/**
* Accepts every file.
*
* @return true
*/
#[ReturnTypeWillChange]
public function accept()
{
return true;

}//end accept()


}//end class
3 changes: 3 additions & 0 deletions tests/Core/Files/FileList/Fixtures/file1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

// Empty file for testing purposes.
3 changes: 3 additions & 0 deletions tests/Core/Files/FileList/Fixtures/file2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php

// Empty file for testing purposes.