Skip to content

Commit 39649c2

Browse files
committed
Allow nonexistent paths in excludePaths by appending (?)
1 parent e2c9b4a commit 39649c2

File tree

6 files changed

+101
-35
lines changed

6 files changed

+101
-35
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,15 @@ jobs:
238238
echo "$OUTPUT"
239239
../bashunit -a contains 'Invalid entry in excludePaths' "$OUTPUT"
240240
../bashunit -a contains 'src/test.php is neither a directory, nor a file path, nor a fnmatch pattern.' "$OUTPUT"
241+
- script: |
242+
cd e2e/bad-exclude-paths
243+
OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon)
244+
echo "$OUTPUT"
245+
- script: |
246+
cd e2e/bad-exclude-paths
247+
cp -r tmp-node-modules node_modules
248+
OUTPUT=$(../../bin/phpstan analyse -c ignoreNonexistentExcludePath.neon)
249+
echo "$OUTPUT"
241250
242251
steps:
243252
- name: "Checkout"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
includes:
2+
- ../../conf/bleedingEdge.neon
3+
4+
parameters:
5+
level: 8
6+
paths:
7+
- .
8+
excludePaths:
9+
- node_modules (?)
10+
- tmp-node-modules
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
echo [];
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection\Neon;
4+
5+
final class OptionalPath
6+
{
7+
8+
public function __construct(public readonly string $path)
9+
{
10+
}
11+
12+
}

src/DependencyInjection/NeonAdapter.php

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
use Nette\Neon\Entity;
1111
use Nette\Neon\Exception;
1212
use Nette\Neon\Neon;
13+
use PHPStan\DependencyInjection\Neon\OptionalPath;
1314
use PHPStan\File\FileHelper;
1415
use PHPStan\File\FileReader;
1516
use function array_values;
1617
use function array_walk_recursive;
18+
use function count;
1719
use function dirname;
1820
use function implode;
1921
use function in_array;
@@ -29,7 +31,7 @@
2931
class NeonAdapter implements Adapter
3032
{
3133

32-
public const CACHE_KEY = 'v26-no-implicit-wildcard';
34+
public const CACHE_KEY = 'v27-optional-path';
3335

3436
private const PREVENT_MERGING_SUFFIX = '!';
3537

@@ -65,6 +67,13 @@ public function process(array $arr, string $fileKey, string $file): array
6567
$val[Helpers::PREVENT_MERGING] = true;
6668
}
6769

70+
$keyToResolve = $fileKey;
71+
if (is_int($key)) {
72+
$keyToResolve .= '[]';
73+
} else {
74+
$keyToResolve .= '[' . $key . ']';
75+
}
76+
6877
if (is_array($val)) {
6978
if (!is_int($key)) {
7079
$fileKeyToPass = $fileKey . '[' . $key . ']';
@@ -89,18 +98,27 @@ public function process(array $arr, string $fileKey, string $file): array
8998
}
9099
$val = $tmp;
91100
} else {
92-
$tmp = $this->process([$val->value], $fileKeyToPass, $file);
93-
$val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file));
101+
if (
102+
in_array($keyToResolve, [
103+
'[parameters][excludePaths][]',
104+
'[parameters][excludePaths][analyse][]',
105+
'[parameters][excludePaths][analyseAndScan][]',
106+
], true)
107+
&& count($val->attributes) === 1
108+
&& $val->attributes[0] === '?'
109+
&& is_string($val->value)
110+
&& !str_contains($val->value, '%')
111+
&& !str_starts_with($val->value, '*')
112+
) {
113+
$fileHelper = $this->createFileHelperByFile($file);
114+
$val = new OptionalPath($fileHelper->normalizePath($fileHelper->absolutizePath($val->value)));
115+
} else {
116+
$tmp = $this->process([$val->value], $fileKeyToPass, $file);
117+
$val = new Statement($tmp[0], $this->process($val->attributes, $fileKeyToPass, $file));
118+
}
94119
}
95120
}
96121

97-
$keyToResolve = $fileKey;
98-
if (is_int($key)) {
99-
$keyToResolve .= '[]';
100-
} else {
101-
$keyToResolve .= '[' . $key . ']';
102-
}
103-
104122
if (in_array($keyToResolve, [
105123
'[parameters][paths][]',
106124
'[parameters][excludes_analyse][]',

src/DependencyInjection/ValidateExcludePathsExtension.php

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
namespace PHPStan\DependencyInjection;
44

55
use Nette\DI\CompilerExtension;
6+
use PHPStan\DependencyInjection\Neon\OptionalPath;
67
use PHPStan\File\FileExcluder;
78
use function array_key_exists;
9+
use function array_map;
810
use function array_merge;
9-
use function array_unique;
1011
use function count;
1112
use function is_dir;
1213
use function is_file;
@@ -21,45 +22,58 @@ class ValidateExcludePathsExtension extends CompilerExtension
2122
public function loadConfiguration(): void
2223
{
2324
$builder = $this->getContainerBuilder();
24-
if (!$builder->parameters['__validate']) {
25-
return;
26-
}
27-
2825
$excludePaths = $builder->parameters['excludePaths'];
2926
if ($excludePaths === null) {
3027
return;
3128
}
3229

30+
$errors = [];
3331
$noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard'];
34-
if (!$noImplicitWildcard) {
35-
return;
32+
if ($builder->parameters['__validate'] && $noImplicitWildcard) {
33+
$paths = [];
34+
if (array_key_exists('analyse', $excludePaths)) {
35+
$paths = $excludePaths['analyse'];
36+
}
37+
if (array_key_exists('analyseAndScan', $excludePaths)) {
38+
$paths = array_merge($paths, $excludePaths['analyseAndScan']);
39+
}
40+
foreach ($paths as $path) {
41+
if ($path instanceof OptionalPath) {
42+
continue;
43+
}
44+
if (FileExcluder::isAbsolutePath($path)) {
45+
if (is_dir($path)) {
46+
continue;
47+
}
48+
if (is_file($path)) {
49+
continue;
50+
}
51+
}
52+
if (FileExcluder::isFnmatchPattern($path)) {
53+
continue;
54+
}
55+
56+
$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path);
57+
}
3658
}
3759

38-
$paths = [];
60+
$newExcludePaths = [];
3961
if (array_key_exists('analyse', $excludePaths)) {
40-
$paths = $excludePaths['analyse'];
62+
$newExcludePaths['analyse'] = $excludePaths['analyse'];
4163
}
4264
if (array_key_exists('analyseAndScan', $excludePaths)) {
43-
$paths = array_merge($paths, $excludePaths['analyseAndScan']);
65+
$newExcludePaths['analyseAndScan'] = $excludePaths['analyseAndScan'];
4466
}
4567

46-
$errors = [];
47-
foreach (array_unique($paths) as $path) {
48-
if (FileExcluder::isAbsolutePath($path)) {
49-
if (is_dir($path)) {
50-
continue;
51-
}
52-
if (is_file($path)) {
53-
continue;
54-
}
55-
}
56-
if (FileExcluder::isFnmatchPattern($path)) {
57-
continue;
58-
}
59-
60-
$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path);
68+
foreach ($newExcludePaths as $key => $p) {
69+
$newExcludePaths[$key] = array_map(
70+
static fn ($path) => $path instanceof OptionalPath ? $path->path : $path,
71+
$p,
72+
);
6173
}
6274

75+
$builder->parameters['excludePaths'] = $newExcludePaths;
76+
6377
if (count($errors) === 0) {
6478
return;
6579
}

0 commit comments

Comments
 (0)