From 39649c2b9db016edabdb813dfe39ae0fd46e0c09 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 3 Aug 2024 14:20:29 +0200 Subject: [PATCH] Allow nonexistent paths in excludePaths by appending `(?)` --- .github/workflows/e2e-tests.yml | 9 +++ .../ignoreNonexistentExcludePath.neon | 10 +++ .../tmp-node-modules/test.php | 3 + src/DependencyInjection/Neon/OptionalPath.php | 12 ++++ src/DependencyInjection/NeonAdapter.php | 38 ++++++++--- .../ValidateExcludePathsExtension.php | 64 +++++++++++-------- 6 files changed, 101 insertions(+), 35 deletions(-) create mode 100644 e2e/bad-exclude-paths/ignoreNonexistentExcludePath.neon create mode 100644 e2e/bad-exclude-paths/tmp-node-modules/test.php create mode 100644 src/DependencyInjection/Neon/OptionalPath.php diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d87dfaa8d7..c5fb603271 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -238,6 +238,15 @@ jobs: echo "$OUTPUT" ../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT" ../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT" + - script: | + cd e2e/bad-exclude-paths + OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon) + echo "$OUTPUT" + - script: | + cd e2e/bad-exclude-paths + cp -r tmp-node-modules node_modules + OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon) + echo "$OUTPUT" steps: - name: "Checkout" diff --git a/e2e/bad-exclude-paths/ignoreNonexistentExcludePath.neon b/e2e/bad-exclude-paths/ignoreNonexistentExcludePath.neon new file mode 100644 index 0000000000..b78c536f97 --- /dev/null +++ b/e2e/bad-exclude-paths/ignoreNonexistentExcludePath.neon @@ -0,0 +1,10 @@ +includes: + - ../../conf/bleedingEdge.neon + +parameters: + level: 8 + paths: + - . + excludePaths: + - node_modules (?) + - tmp-node-modules diff --git a/e2e/bad-exclude-paths/tmp-node-modules/test.php b/e2e/bad-exclude-paths/tmp-node-modules/test.php new file mode 100644 index 0000000000..c24b518fb0 --- /dev/null +++ b/e2e/bad-exclude-paths/tmp-node-modules/test.php @@ -0,0 +1,3 @@ +process([$val->value], $fileKeyToPass, $file); - $val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file)); + if ( + in_array($keyToResolve, [ + '[parameters][excludePaths][]', + '[parameters][excludePaths][analyse][]', + '[parameters][excludePaths][analyseAndScan][]', + ], true) + && count($val->attributes) === 1 + && $val->attributes[0] === '?' + && is_string($val->value) + && !str_contains($val->value, '%') + && !str_starts_with($val->value, '*') + ) { + $fileHelper = $this->createFileHelperByFile($file); + $val = new OptionalPath($fileHelper->normalizePath($fileHelper->absolutizePath($val->value))); + } else { + $tmp = $this->process([$val->value], $fileKeyToPass, $file); + $val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file)); + } } } - $keyToResolve = $fileKey; - if (is_int($key)) { - $keyToResolve .= '[]'; - } else { - $keyToResolve .= '[' . $key . ']'; - } - if (in_array($keyToResolve, [ '[parameters][paths][]', '[parameters][excludes_analyse][]', diff --git a/src/DependencyInjection/ValidateExcludePathsExtension.php b/src/DependencyInjection/ValidateExcludePathsExtension.php index cbc358607e..1b88291fda 100644 --- a/src/DependencyInjection/ValidateExcludePathsExtension.php +++ b/src/DependencyInjection/ValidateExcludePathsExtension.php @@ -3,10 +3,11 @@ namespace PHPStan\DependencyInjection; use Nette\DI\CompilerExtension; +use PHPStan\DependencyInjection\Neon\OptionalPath; use PHPStan\File\FileExcluder; use function array_key_exists; +use function array_map; use function array_merge; -use function array_unique; use function count; use function is_dir; use function is_file; @@ -21,45 +22,58 @@ class ValidateExcludePathsExtension extends CompilerExtension public function loadConfiguration(): void { $builder = $this->getContainerBuilder(); - if (!$builder->parameters['__validate']) { - return; - } - $excludePaths = $builder->parameters['excludePaths']; if ($excludePaths === null) { return; } + $errors = []; $noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard']; - if (!$noImplicitWildcard) { - return; + if ($builder->parameters['__validate'] && $noImplicitWildcard) { + $paths = []; + if (array_key_exists('analyse', $excludePaths)) { + $paths = $excludePaths['analyse']; + } + if (array_key_exists('analyseAndScan', $excludePaths)) { + $paths = array_merge($paths, $excludePaths['analyseAndScan']); + } + foreach ($paths as $path) { + if ($path instanceof OptionalPath) { + continue; + } + if (FileExcluder::isAbsolutePath($path)) { + if (is_dir($path)) { + continue; + } + if (is_file($path)) { + continue; + } + } + if (FileExcluder::isFnmatchPattern($path)) { + continue; + } + + $errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path); + } } - $paths = []; + $newExcludePaths = []; if (array_key_exists('analyse', $excludePaths)) { - $paths = $excludePaths['analyse']; + $newExcludePaths['analyse'] = $excludePaths['analyse']; } if (array_key_exists('analyseAndScan', $excludePaths)) { - $paths = array_merge($paths, $excludePaths['analyseAndScan']); + $newExcludePaths['analyseAndScan'] = $excludePaths['analyseAndScan']; } - $errors = []; - foreach (array_unique($paths) as $path) { - if (FileExcluder::isAbsolutePath($path)) { - if (is_dir($path)) { - continue; - } - if (is_file($path)) { - continue; - } - } - if (FileExcluder::isFnmatchPattern($path)) { - continue; - } - - $errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path); + foreach ($newExcludePaths as $key => $p) { + $newExcludePaths[$key] = array_map( + static fn ($path) => $path instanceof OptionalPath ? $path->path : $path, + $p, + ); } + $builder->parameters['excludePaths'] = $newExcludePaths; + if (count($errors) === 0) { return; }