Skip to content

Commit e19e6e5

Browse files
committed
Bleeding edge - no implicit wildcard in FileExcluder
See phpstan/phpstan#10299
1 parent 35a2f57 commit e19e6e5

13 files changed

+261
-13
lines changed

build/phpstan.neon

-4
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,13 @@ parameters:
2222
checkUninitializedProperties: true
2323
checkMissingCallableSignature: true
2424
excludePaths:
25-
- ../src/Reflection/SignatureMap/functionMap.php
26-
- ../src/Reflection/SignatureMap/functionMetadata.php
2725
- ../tests/*/data/*
2826
- ../tests/tmp/*
2927
- ../tests/PHPStan/Analyser/nsrt/*
3028
- ../tests/PHPStan/Analyser/traits/*
3129
- ../tests/notAutoloaded/*
32-
- ../tests/PHPStan/Generics/functions.php
3330
- ../tests/PHPStan/Reflection/UnionTypesTest.php
3431
- ../tests/PHPStan/Reflection/MixedTypeTest.php
35-
- ../tests/PHPStan/Reflection/StaticTypeTest.php
3632
- ../tests/e2e/magic-setter/*
3733
- ../tests/PHPStan/Rules/Properties/UninitializedPropertyRuleTest.php
3834
- ../tests/PHPStan/Command/IgnoredRegexValidatorTest.php

conf/bleedingEdge.neon

+1
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,6 @@ parameters:
5959
printfArrayParameters: true
6060
preciseMissingReturn: true
6161
validatePregQuote: true
62+
noImplicitWildcard: true
6263
stubFiles:
6364
- ../stubs/bleedingEdge/Rule.stub

conf/config.neon

+4
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ parameters:
9494
printfArrayParameters: false
9595
preciseMissingReturn: false
9696
validatePregQuote: false
97+
noImplicitWildcard: false
9798
fileExtensions:
9899
- php
99100
checkAdvancedIsset: false
@@ -269,6 +270,7 @@ extensions:
269270
conditionalTags: PHPStan\DependencyInjection\ConditionalTagsExtension
270271
parametersSchema: PHPStan\DependencyInjection\ParametersSchemaExtension
271272
validateIgnoredErrors: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension
273+
validateExcludePaths: PHPStan\DependencyInjection\ValidateExcludePathsExtension
272274

273275
rules:
274276
- PHPStan\Rules\Debug\DumpTypeRule
@@ -676,6 +678,8 @@ services:
676678

677679
-
678680
implement: PHPStan\File\FileExcluderRawFactory
681+
arguments:
682+
noImplicitWildcard: %featureToggles.noImplicitWildcard%
679683

680684
fileExcluderAnalyse:
681685
class: PHPStan\File\FileExcluder

conf/parametersSchema.neon

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ parametersSchema:
8989
printfArrayParameters: bool()
9090
preciseMissingReturn: bool()
9191
validatePregQuote: bool()
92+
noImplicitWildcard: bool()
9293
])
9394
fileExtensions: listOf(string())
9495
checkAdvancedIsset: bool()

src/Analyser/Ignore/IgnoredError.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Nette\Utils\Strings;
66
use PHPStan\Analyser\Error;
7+
use PHPStan\DependencyInjection\BleedingEdgeToggle;
78
use PHPStan\File\FileExcluder;
89
use PHPStan\File\FileHelper;
910
use PHPStan\ShouldNotHappenException;
@@ -85,7 +86,7 @@ public static function shouldIgnore(
8586
}
8687

8788
if ($path !== null) {
88-
$fileExcluder = new FileExcluder($fileHelper, [$path]);
89+
$fileExcluder = new FileExcluder($fileHelper, [$path], BleedingEdgeToggle::isBleedingEdge());
8990
$isExcluded = $fileExcluder->isExcludedFromAnalysing($error->getFilePath());
9091
if (!$isExcluded && $error->getTraitFilePath() !== null) {
9192
return $fileExcluder->isExcludedFromAnalysing($error->getTraitFilePath());

src/Command/CommandHelper.php

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use PHPStan\DependencyInjection\Container;
1919
use PHPStan\DependencyInjection\ContainerFactory;
2020
use PHPStan\DependencyInjection\DuplicateIncludedFilesException;
21+
use PHPStan\DependencyInjection\InvalidExcludePathsException;
2122
use PHPStan\DependencyInjection\InvalidIgnoredErrorPatternsException;
2223
use PHPStan\DependencyInjection\LoaderFactory;
2324
use PHPStan\ExtensionInstaller\GeneratedConfig;
@@ -355,6 +356,13 @@ public static function begin(
355356
$errorOutput->writeLineFormatted('');
356357
}
357358
throw new InceptionNotSuccessfulException();
359+
} catch (InvalidExcludePathsException $e) {
360+
$errorOutput->writeLineFormatted(sprintf('<error>Invalid %s in excludePaths:</error>', count($e->getErrors()) === 1 ? 'entry' : 'entries'));
361+
foreach ($e->getErrors() as $error) {
362+
$errorOutput->writeLineFormatted($error);
363+
$errorOutput->writeLineFormatted('');
364+
}
365+
throw new InceptionNotSuccessfulException();
358366
} catch (ValidationException $e) {
359367
foreach ($e->getMessages() as $message) {
360368
$errorOutput->writeLineFormatted('<error>Invalid configuration:</error>');
@@ -583,7 +591,7 @@ public static function begin(
583591

584592
$pathRoutingParser->setAnalysedFiles($files);
585593

586-
$stubFilesExcluder = new FileExcluder($currentWorkingDirectoryFileHelper, $stubFilesProvider->getProjectStubFiles());
594+
$stubFilesExcluder = new FileExcluder($currentWorkingDirectoryFileHelper, $stubFilesProvider->getProjectStubFiles(), true);
587595

588596
$files = array_values(array_filter($files, static fn (string $file) => !$stubFilesExcluder->isExcludedFromAnalysing($file)));
589597

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection;
4+
5+
use Exception;
6+
use function implode;
7+
8+
class InvalidExcludePathsException extends Exception
9+
{
10+
11+
/**
12+
* @param string[] $errors
13+
*/
14+
public function __construct(private array $errors)
15+
{
16+
parent::__construct(implode("\n", $this->errors));
17+
}
18+
19+
/**
20+
* @return string[]
21+
*/
22+
public function getErrors(): array
23+
{
24+
return $this->errors;
25+
}
26+
27+
}

src/DependencyInjection/NeonAdapter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
class NeonAdapter implements Adapter
3030
{
3131

32-
public const CACHE_KEY = 'v25-nette-di-again';
32+
public const CACHE_KEY = 'v26-no-implicit-wildcard';
3333

3434
private const PREVENT_MERGING_SUFFIX = '!';
3535

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection;
4+
5+
use Nette\DI\CompilerExtension;
6+
use PHPStan\File\FileExcluder;
7+
use function array_key_exists;
8+
use function array_merge;
9+
use function array_unique;
10+
use function count;
11+
use function is_dir;
12+
use function is_file;
13+
use function sprintf;
14+
15+
class ValidateExcludePathsExtension extends CompilerExtension
16+
{
17+
18+
/**
19+
* @throws InvalidExcludePathsException
20+
*/
21+
public function loadConfiguration(): void
22+
{
23+
$builder = $this->getContainerBuilder();
24+
if (!$builder->parameters['__validate']) {
25+
return;
26+
}
27+
28+
$excludePaths = $builder->parameters['excludePaths'];
29+
if ($excludePaths === null) {
30+
return;
31+
}
32+
33+
$noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard'];
34+
if (!$noImplicitWildcard) {
35+
return;
36+
}
37+
38+
$paths = [];
39+
if (array_key_exists('analyse', $excludePaths)) {
40+
$paths = $excludePaths['analyse'];
41+
}
42+
if (array_key_exists('analyseAndScan', $excludePaths)) {
43+
$paths = array_merge($paths, $excludePaths['analyseAndScan']);
44+
}
45+
46+
$errors = [];
47+
foreach (array_unique($paths) as $path) {
48+
if (is_dir($path)) {
49+
continue;
50+
}
51+
if (is_file($path)) {
52+
continue;
53+
}
54+
if (FileExcluder::isFnmatchPattern($path)) {
55+
continue;
56+
}
57+
58+
$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $path);
59+
}
60+
61+
if (count($errors) === 0) {
62+
return;
63+
}
64+
65+
throw new InvalidExcludePathsException($errors);
66+
}
67+
68+
}

src/DependencyInjection/ValidateIgnoredErrorsExtension.php

+35
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Analyser\NameScope;
1212
use PHPStan\Command\IgnoredRegexValidator;
1313
use PHPStan\DependencyInjection\Type\OperatorTypeSpecifyingExtensionRegistryProvider;
14+
use PHPStan\File\FileExcluder;
1415
use PHPStan\Php\PhpVersion;
1516
use PHPStan\PhpDoc\DirectTypeNodeResolverExtensionRegistryProvider;
1617
use PHPStan\PhpDoc\TypeNodeResolver;
@@ -34,6 +35,8 @@
3435
use function count;
3536
use function implode;
3637
use function is_array;
38+
use function is_dir;
39+
use function is_file;
3740
use function sprintf;
3841
use const PHP_VERSION_ID;
3942

@@ -55,6 +58,8 @@ public function loadConfiguration(): void
5558
return;
5659
}
5760

61+
$noImplicitWildcard = $builder->parameters['featureToggles']['noImplicitWildcard'];
62+
5863
/** @throws void */
5964
$parser = Llk::load(new Read(__DIR__ . '/../../resources/RegexGrammar.pp'));
6065
$reflectionProvider = new DummyReflectionProvider();
@@ -131,6 +136,36 @@ public function getRegistry(): OperatorTypeSpecifyingExtensionRegistry
131136
}
132137
}
133138

139+
if ($noImplicitWildcard) {
140+
foreach ($ignoreErrors as $ignoreError) {
141+
if (!is_array($ignoreError)) {
142+
continue;
143+
}
144+
145+
if (isset($ignoreError['path'])) {
146+
$ignorePaths = [$ignoreError['path']];
147+
} elseif (isset($ignoreError['paths'])) {
148+
$ignorePaths = $ignoreError['paths'];
149+
} else {
150+
continue;
151+
}
152+
153+
foreach ($ignorePaths as $ignorePath) {
154+
if (is_dir($ignorePath)) {
155+
continue;
156+
}
157+
if (is_file($ignorePath)) {
158+
continue;
159+
}
160+
if (FileExcluder::isFnmatchPattern($ignorePath)) {
161+
continue;
162+
}
163+
164+
$errors[] = sprintf('Path %s is neither a directory, nor a file path, nor a fnmatch pattern.', $ignorePath);
165+
}
166+
}
167+
}
168+
134169
if (count($errors) === 0) {
135170
return;
136171
}

src/File/FileExcluder.php

+45-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use function fnmatch;
66
use function in_array;
7+
use function is_dir;
8+
use function is_file;
79
use function preg_match;
810
use function str_starts_with;
911
use function strlen;
@@ -15,12 +17,26 @@ class FileExcluder
1517
{
1618

1719
/**
18-
* Directories to exclude from analysing
20+
* Paths to exclude from analysing
1921
*
2022
* @var string[]
2123
*/
2224
private array $literalAnalyseExcludes = [];
2325

26+
/**
27+
* Directories to exclude from analysing
28+
*
29+
* @var string[]
30+
*/
31+
private array $literalAnalyseDirectoryExcludes = [];
32+
33+
/**
34+
* Files to exclude from analysing
35+
*
36+
* @var string[]
37+
*/
38+
private array $literalAnalyseFilesExcludes = [];
39+
2440
/**
2541
* fnmatch() patterns to use for excluding files and directories from analysing
2642
* @var string[]
@@ -35,6 +51,7 @@ class FileExcluder
3551
public function __construct(
3652
FileHelper $fileHelper,
3753
array $analyseExcludes,
54+
private bool $noImplicitWildcard,
3855
)
3956
{
4057
foreach ($analyseExcludes as $exclude) {
@@ -47,10 +64,22 @@ public function __construct(
4764
$normalized .= DIRECTORY_SEPARATOR;
4865
}
4966

50-
if ($this->isFnmatchPattern($normalized)) {
67+
if (self::isFnmatchPattern($normalized)) {
5168
$this->fnmatchAnalyseExcludes[] = $normalized;
5269
} else {
53-
$this->literalAnalyseExcludes[] = $fileHelper->absolutizePath($normalized);
70+
if ($this->noImplicitWildcard) {
71+
if (is_file($normalized)) {
72+
$this->literalAnalyseFilesExcludes[] = $normalized;
73+
} elseif (is_dir($normalized)) {
74+
if (!$trailingDirSeparator) {
75+
$normalized .= DIRECTORY_SEPARATOR;
76+
}
77+
78+
$this->literalAnalyseDirectoryExcludes[] = $normalized;
79+
}
80+
} else {
81+
$this->literalAnalyseExcludes[] = $fileHelper->absolutizePath($normalized);
82+
}
5483
}
5584
}
5685

@@ -69,6 +98,18 @@ public function isExcludedFromAnalysing(string $file): bool
6998
return true;
7099
}
71100
}
101+
if ($this->noImplicitWildcard) {
102+
foreach ($this->literalAnalyseDirectoryExcludes as $exclude) {
103+
if (str_starts_with($file, $exclude)) {
104+
return true;
105+
}
106+
}
107+
foreach ($this->literalAnalyseFilesExcludes as $exclude) {
108+
if ($file === $exclude) {
109+
return true;
110+
}
111+
}
112+
}
72113
foreach ($this->fnmatchAnalyseExcludes as $exclude) {
73114
if (fnmatch($exclude, $file, $this->fnmatchFlags)) {
74115
return true;
@@ -78,7 +119,7 @@ public function isExcludedFromAnalysing(string $file): bool
78119
return false;
79120
}
80121

81-
private function isFnmatchPattern(string $path): bool
122+
public static function isFnmatchPattern(string $path): bool
82123
{
83124
return preg_match('~[*?[\]]~', $path) > 0;
84125
}

0 commit comments

Comments
 (0)