Skip to content

Commit 10c222a

Browse files
committed
Fix inconsistent analysis on case-insensitive filesystems
Update e2e-tests.yml Update e2e-tests.yml Update e2e-tests.yml Update e2e-tests.yml Update e2e-tests.yml Update e2e-tests.yml Update e2e-tests.yml fix PathRoutingParser extract and re-use AnalysedFilesResolver use AnalysedFilesResolver more use AnalysedFilesResolver more simplify test also on windows added failling ubuntu test added feature detect simplify detect more assertions simplify Update AnalysedFilesResolver.php
1 parent ea816fc commit 10c222a

18 files changed

+186
-54
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ jobs:
308308

309309
e2e-tests:
310310
name: "E2E tests"
311-
runs-on: "ubuntu-latest"
311+
runs-on: ${{ matrix.os }}
312312
timeout-minutes: 60
313313

314314
strategy:
@@ -317,84 +317,127 @@ jobs:
317317
- script: "bin/phpstan analyse -l 8 -a tests/e2e/data/timecop.php -c tests/e2e/data/empty.neon tests/e2e/data/timecop.php"
318318
tools: "pecl"
319319
extensions: "timecop-beta"
320+
os: "ubuntu-latest"
320321
- script: "bin/phpstan analyse -l 8 -a tests/e2e/data/soap.php -c tests/e2e/data/empty.neon tests/e2e/data/soap.php"
321322
extensions: "soap"
323+
os: "ubuntu-latest"
322324
- script: "bin/phpstan analyse -l 8 -a tests/e2e/data/soap.php -c tests/e2e/data/empty.neon tests/e2e/data/soap.php"
323325
extensions: ""
326+
os: "ubuntu-latest"
324327
- script: "bin/phpstan analyse -l 8 tests/e2e/anon-class/Granularity.php"
325328
extensions: ""
329+
os: "ubuntu-latest"
326330
- script: "bin/phpstan analyse -l 8 e2e/phpstan-phpunit-190/test.php -c e2e/phpstan-phpunit-190/test.neon"
327331
extensions: ""
332+
os: "ubuntu-latest"
328333
- script: "bin/phpstan analyse e2e/only-files-not-analysed-trait/src -c e2e/only-files-not-analysed-trait/ignore.neon"
329334
extensions: ""
335+
os: "ubuntu-latest"
330336
- script: "bin/phpstan analyse e2e/only-files-not-analysed-trait/src/Foo.php e2e/only-files-not-analysed-trait/src/BarTrait.php -c e2e/only-files-not-analysed-trait/no-ignore.neon"
331337
extensions: ""
338+
os: "ubuntu-latest"
332339
- script: |
333340
cd e2e/baseline-uninit-prop-trait
334341
../../bin/phpstan analyse --debug --configuration test-no-baseline.neon --generate-baseline test-baseline.neon
335342
../../bin/phpstan analyse --debug --configuration test.neon
343+
os: "ubuntu-latest"
336344
- script: |
337345
cd e2e/baseline-uninit-prop-trait
338346
../../bin/phpstan analyse --configuration test-no-baseline.neon --generate-baseline test-baseline.neon
339347
../../bin/phpstan analyse --configuration test.neon
348+
os: "ubuntu-latest"
340349
- script: |
341350
cd e2e/discussion-11362
342351
composer install
343352
../../bin/phpstan
353+
os: "ubuntu-latest"
344354
- script: |
345355
cd e2e/bug-11819
346356
../../bin/phpstan
357+
os: "ubuntu-latest"
347358
- script: |
348359
cd e2e/composer-and-phpstan-version-config
349360
composer install --ignore-platform-reqs
350361
../../bin/phpstan analyze test.php --level=0
362+
os: "ubuntu-latest"
351363
- script: |
352364
cd e2e/composer-max-version
353365
composer install
354366
../../bin/phpstan analyze test.php --level=0
367+
os: "ubuntu-latest"
355368
- script: |
356369
cd e2e/composer-min-max-version
357370
composer install
358371
../../bin/phpstan analyze test.php --level=0
372+
os: "ubuntu-latest"
359373
- script: |
360374
cd e2e/composer-min-open-end-version
361375
composer install
362376
../../bin/phpstan analyze test.php --level=0
377+
os: "ubuntu-latest"
363378
- script: |
364379
cd e2e/composer-min-version-v5
365380
composer install --ignore-platform-reqs
366381
../../bin/phpstan analyze test.php --level=0
382+
os: "ubuntu-latest"
367383
- script: |
368384
cd e2e/composer-min-version-v7
369385
composer install --ignore-platform-reqs
370386
../../bin/phpstan analyze test.php --level=0
387+
os: "ubuntu-latest"
371388
- script: |
372389
cd e2e/composer-min-version
373390
composer install
374391
../../bin/phpstan analyze test.php --level=0
392+
os: "ubuntu-latest"
375393
- script: |
376394
cd e2e/composer-no-versions
377395
composer install
378396
../../bin/phpstan analyze test.php --level=0
397+
os: "ubuntu-latest"
379398
- script: |
380399
cd e2e/composer-version-config-invalid
381400
OUTPUT=$(../bashunit -a exit_code "1" ../../bin/phpstan)
382401
echo "$OUTPUT"
383402
../bashunit -a contains 'Invalid configuration' "$OUTPUT"
384403
../bashunit -a contains 'Invalid PHP version range: phpVersion.max should be greater or equal to phpVersion.min.' "$OUTPUT"
404+
os: "ubuntu-latest"
385405
- script: |
386406
cd e2e/composer-version-config-patch
387407
composer install --ignore-platform-reqs
388408
../../bin/phpstan analyze test.php --level=0
409+
os: "ubuntu-latest"
389410
- script: |
390411
cd e2e/composer-version-config
391412
composer install
392413
../../bin/phpstan analyze test.php --level=0
414+
os: "ubuntu-latest"
415+
- script: |
416+
cd e2e/bug-12972
417+
OUTPUT=$(../bashunit -a exit_code "1" ../../bin/phpstan)
418+
echo "$OUTPUT"
419+
../bashunit -a contains 'Internal error: Failed opening required' "$OUTPUT"
420+
../bashunit -a contains 'e2e/bug-12972/src/OTHER/file.php' "$OUTPUT"
421+
os: "ubuntu-latest"
422+
- script: |
423+
cd e2e/bug-12972
424+
../../bin/phpstan analyze
425+
os: "macos-latest"
426+
- script: |
427+
cd e2e/bug-12972
428+
../../bin/phpstan analyze
429+
os: "windows-latest"
393430
394431
steps:
395432
- name: "Checkout"
396433
uses: actions/checkout@v4
397434

435+
- name: "Install GNU Patch on macOS" # see https://github.com/cweagans/composer-patches/issues/326
436+
if: runner.os == 'macOS'
437+
run: |
438+
brew install gpatch
439+
echo "/opt/homebrew/opt/gpatch/libexec/gnubin" >> $GITHUB_PATH
440+
398441
- name: "Install PHP"
399442
uses: "shivammathur/setup-php@v2"
400443
with:

e2e/bug-12972/autoloader.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
spl_autoload_register(function($class) {
4+
if ($class === \other12972\MyClass::class) {
5+
require_once __DIR__ . '/src/OTHER/file.php'; // wrong case sensitivity on purpose
6+
}
7+
});

e2e/bug-12972/phpstan.dist.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
parameters:
2+
level: 9
3+
4+
paths:
5+
- src
6+
7+
bootstrapFiles:
8+
- autoloader.php

e2e/bug-12972/src/folder/file2.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Foo12972;
4+
5+
use other12972\MyClass;
6+
7+
function doBar(MyClass $myClass):void {}

e2e/bug-12972/src/other/file.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace other12972;
4+
5+
class MyClass {
6+
public function doSomething(): int
7+
{
8+
return 1;
9+
}
10+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Analyser;
4+
5+
use PHPStan\File\FilesystemHelper;
6+
use function array_fill_keys;
7+
use function array_map;
8+
use function strtolower;
9+
10+
final class AnalysedFilesResolver
11+
{
12+
13+
/** @var bool[] filePath(string) => bool(true) */
14+
private array $analysedFiles;
15+
16+
/**
17+
* @param string[] $files
18+
*/
19+
public function __construct(array $files = [])
20+
{
21+
$this->setAnalysedFiles($files);
22+
}
23+
24+
/**
25+
* @param string[] $files
26+
*/
27+
public function setAnalysedFiles(array $files): void
28+
{
29+
if (FilesystemHelper::isCaseSensitive() === false) {
30+
$files = array_map(static fn (string $file): string => strtolower($file), $files);
31+
}
32+
$this->analysedFiles = array_fill_keys($files, true);
33+
}
34+
35+
public function isInAnalyzedFiles(string $file): bool
36+
{
37+
if (FilesystemHelper::isCaseSensitive() === false) {
38+
$file = strtolower($file);
39+
}
40+
41+
return isset($this->analysedFiles[$file]);
42+
}
43+
44+
}

src/Analyser/Analyser.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\Collectors\Registry as CollectorRegistry;
88
use PHPStan\Rules\Registry as RuleRegistry;
99
use Throwable;
10-
use function array_fill_keys;
1110
use function array_merge;
1211
use function count;
1312
use function memory_get_peak_usage;
@@ -47,7 +46,7 @@ public function analyse(
4746
}
4847

4948
$this->nodeScopeResolver->setAnalysedFiles($allAnalysedFiles);
50-
$allAnalysedFiles = array_fill_keys($allAnalysedFiles, true);
49+
$analyzedFilesResolver = new AnalysedFilesResolver($allAnalysedFiles);
5150

5251
/** @var list<Error> $errors */
5352
$errors = [];
@@ -78,7 +77,7 @@ public function analyse(
7877
try {
7978
$fileAnalyserResult = $this->fileAnalyser->analyseFile(
8079
$file,
81-
$allAnalysedFiles,
80+
$analyzedFilesResolver,
8281
$this->ruleRegistry,
8382
$this->collectorRegistry,
8483
null,

src/Analyser/FileAnalyser.php

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,11 @@ public function __construct(
6363
}
6464

6565
/**
66-
* @param array<string, true> $analysedFiles
6766
* @param callable(Node $node, Scope $scope): void|null $outerNodeCallback
6867
*/
6968
public function analyseFile(
7069
string $file,
71-
array $analysedFiles,
70+
AnalysedFilesResolver $analysedFilesResolver,
7271
RuleRegistry $ruleRegistry,
7372
CollectorRegistry $collectorRegistry,
7473
?callable $outerNodeCallback,
@@ -88,13 +87,14 @@ public function analyseFile(
8887
$exportedNodes = [];
8988
$linesToIgnore = [];
9089
$unmatchedLineIgnores = [];
90+
9191
if (is_file($file)) {
9292
try {
93-
$this->collectErrors($analysedFiles);
93+
$this->collectErrors($analysedFilesResolver);
9494
$parserNodes = $this->parser->parseFile($file);
9595
$linesToIgnore = $unmatchedLineIgnores = [$file => $this->getLinesToIgnoreFromTokens($parserNodes)];
9696
$temporaryFileErrors = [];
97-
$nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFiles, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors): void {
97+
$nodeCallback = function (Node $node, Scope $scope) use (&$fileErrors, &$fileCollectedData, &$fileDependencies, &$usedTraitFileDependencies, &$exportedNodes, $file, $ruleRegistry, $collectorRegistry, $outerNodeCallback, $analysedFilesResolver, &$linesToIgnore, &$unmatchedLineIgnores, &$temporaryFileErrors): void {
9898
if ($node instanceof Node\Stmt\Trait_) {
9999
foreach (array_keys($linesToIgnore[$file] ?? []) as $lineToIgnore) {
100100
if ($lineToIgnore < $node->getStartLine() || $lineToIgnore > $node->getEndLine()) {
@@ -205,7 +205,7 @@ public function analyseFile(
205205

206206
try {
207207
$dependencies = $this->dependencyResolver->resolveDependencies($node, $scope);
208-
foreach ($dependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) {
208+
foreach ($dependencies->getFileDependencies($scope->getFile(), $analysedFilesResolver) as $dependentFile) {
209209
$fileDependencies[] = $dependentFile;
210210
}
211211
if ($dependencies->getExportedNode() !== null) {
@@ -224,7 +224,7 @@ public function analyseFile(
224224
}
225225

226226
$usedTraitDependencies = $this->dependencyResolver->resolveUsedTraitDependencies($node);
227-
foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $analysedFiles) as $dependentFile) {
227+
foreach ($usedTraitDependencies->getFileDependencies($scope->getFile(), $analysedFilesResolver) as $dependentFile) {
228228
$usedTraitFileDependencies[] = $dependentFile;
229229
}
230230
};
@@ -330,14 +330,11 @@ private function getLinesToIgnoreFromTokens(array $nodes): array
330330
return $nodes[0]->getAttribute('linesToIgnore', []);
331331
}
332332

333-
/**
334-
* @param array<string, true> $analysedFiles
335-
*/
336-
private function collectErrors(array $analysedFiles): void
333+
private function collectErrors(AnalysedFilesResolver $analyzedFilesResolver): void
337334
{
338335
$this->filteredPhpErrors = [];
339336
$this->allPhpErrors = [];
340-
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($analysedFiles): bool {
337+
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($analyzedFilesResolver): bool {
341338
if ((error_reporting() & $errno) === 0) {
342339
// silence @ operator
343340
return true;
@@ -351,7 +348,7 @@ private function collectErrors(array $analysedFiles): void
351348
return true;
352349
}
353350

354-
if (!isset($analysedFiles[$errfile])) {
351+
if (!$analyzedFilesResolver->isInAnalyzedFiles($errfile)) {
355352
return true;
356353
}
357354

src/Analyser/Ignore/IgnoredErrorHelperResult.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
namespace PHPStan\Analyser\Ignore;
44

5+
use PHPStan\Analyser\AnalysedFilesResolver;
56
use PHPStan\Analyser\Error;
67
use PHPStan\File\FileHelper;
78
use PHPStan\ShouldNotHappenException;
8-
use function array_fill_keys;
9-
use function array_key_exists;
109
use function array_values;
1110
use function count;
1211
use function is_array;
@@ -43,12 +42,11 @@ public function getErrors(): array
4342

4443
/**
4544
* @param Error[] $errors
46-
* @param string[] $analysedFiles
4745
*/
4846
public function process(
4947
array $errors,
5048
bool $onlyFiles,
51-
array $analysedFiles,
49+
AnalysedFilesResolver $analysedFilesResolver,
5250
bool $hasInternalErrors,
5351
): IgnoredErrorHelperProcessedResult
5452
{
@@ -190,8 +188,6 @@ public function process(
190188
), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false))->withIdentifier('ignore.count');
191189
}
192190

193-
$analysedFilesKeys = array_fill_keys($analysedFiles, true);
194-
195191
if (!$hasInternalErrors) {
196192
foreach ($unmatchedIgnoredErrors as $unmatchedIgnoredError) {
197193
$reportUnmatched = $unmatchedIgnoredError['reportUnmatched'] ?? $this->reportUnmatchedIgnoredErrors;
@@ -214,7 +210,8 @@ public function process(
214210
), $unmatchedIgnoredError['file'], $unmatchedIgnoredError['line'], false))->withIdentifier('ignore.count');
215211
}
216212
} elseif (isset($unmatchedIgnoredError['realPath'])) {
217-
if (!array_key_exists($unmatchedIgnoredError['realPath'], $analysedFilesKeys)) {
213+
214+
if (!$analysedFilesResolver->isInAnalyzedFiles($unmatchedIgnoredError['realPath'])) {
218215
continue;
219216
}
220217

0 commit comments

Comments
 (0)