diff --git a/config.xsd b/config.xsd
index ab37ca2dcd3..cc51ba82ca3 100644
--- a/config.xsd
+++ b/config.xsd
@@ -144,6 +144,7 @@
+
diff --git a/src/Psalm/Config/FileFilter.php b/src/Psalm/Config/FileFilter.php
index 56ca9f68e7d..1bdaa24f337 100644
--- a/src/Psalm/Config/FileFilter.php
+++ b/src/Psalm/Config/FileFilter.php
@@ -2,6 +2,7 @@
namespace Psalm\Config;
+use FilesystemIterator;
use Psalm\Exception\ConfigException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
@@ -112,6 +113,7 @@ public static function loadFromArray(
foreach ($config['directory'] as $directory) {
$directory_path = (string) ($directory['name'] ?? '');
$ignore_type_stats = (bool) ($directory['ignoreTypeStats'] ?? false);
+ $resolve_symlinks = (bool) ($directory['resolveSymlinks'] ?? false);
$declare_strict_types = (bool) ($directory['useStrictTypes'] ?? false);
if ($directory_path[0] === '/' && DIRECTORY_SEPARATOR === '/') {
@@ -182,27 +184,33 @@ public static function loadFromArray(
);
}
- /** @var RecursiveDirectoryIterator */
- $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory_path));
- $iterator->rewind();
+ if ($resolve_symlinks) {
+ /** @var RecursiveDirectoryIterator */
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($directory_path, FilesystemIterator::SKIP_DOTS)
+ );
+ $iterator->rewind();
- while ($iterator->valid()) {
- if (!$iterator->isDot() && $iterator->isLink()) {
- $linked_path = readlink($iterator->getPathname());
+ while ($iterator->valid()) {
+ if ($iterator->isLink()) {
+ $linked_path = readlink($iterator->getPathname());
- if (stripos($linked_path, $directory_path) !== 0) {
- if ($ignore_type_stats && $filter instanceof ProjectFileFilter) {
- $filter->ignore_type_stats[$directory_path] = true;
- }
+ if (stripos($linked_path, $directory_path) !== 0) {
+ if ($ignore_type_stats && $filter instanceof ProjectFileFilter) {
+ $filter->ignore_type_stats[$directory_path] = true;
+ }
- if ($declare_strict_types && $filter instanceof ProjectFileFilter) {
- $filter->declare_strict_types[$directory_path] = true;
- }
+ if ($declare_strict_types && $filter instanceof ProjectFileFilter) {
+ $filter->declare_strict_types[$directory_path] = true;
+ }
- if (is_dir($linked_path)) {
- $filter->addDirectory($linked_path);
+ if (is_dir($linked_path)) {
+ $filter->addDirectory($linked_path);
+ }
}
}
+
+ $iterator->next();
}
$iterator->next();
@@ -347,6 +355,7 @@ public static function loadFromXMLElement(
$config['directory'][] = [
'name' => (string) $directory['name'],
'ignoreTypeStats' => strtolower((string) ($directory['ignoreTypeStats'] ?? '')) === 'true',
+ 'resolveSymlinks' => strtolower((string) ($directory['resolveSymlinks'] ?? '')) === 'true',
'useStrictTypes' => strtolower((string) ($directory['useStrictTypes'] ?? '')) === 'true',
];
}
diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php
index ed55e000f87..9712f3ba57b 100644
--- a/tests/Config/ConfigTest.php
+++ b/tests/Config/ConfigTest.php
@@ -181,7 +181,7 @@ public function testIgnoreSymlinkedProjectDirectory(): void
-
+
'