Skip to content

Commit c9a6d2e

Browse files
committed
PathRoutingParser - check if the file is a symlink that might be in analysed paths
See phpstan/phpstan#11362
1 parent 101467d commit c9a6d2e

File tree

12 files changed

+367
-0
lines changed

12 files changed

+367
-0
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ jobs:
270270
cd e2e/baseline-uninit-prop-trait
271271
../../bin/phpstan analyse --configuration test-no-baseline.neon --generate-baseline test-baseline.neon
272272
../../bin/phpstan analyse --configuration test.neon
273+
- script: |
274+
cd e2e/discussion-11362
275+
composer install
276+
../../bin/phpstan
273277
274278
steps:
275279
- name: "Checkout"

e2e/discussion-11362/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor

e2e/discussion-11362/composer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"config": {
3+
"preferred-install": {
4+
"*": "dist",
5+
"repro/*": "source"
6+
},
7+
"sort-packages": true
8+
},
9+
"minimum-stability": "dev",
10+
"prefer-stable": true,
11+
"repositories": [
12+
{
13+
"type": "path",
14+
"url": "./packages/*/"
15+
}
16+
],
17+
"require": {
18+
"repro/site": "@dev",
19+
"php": "^8.1"
20+
},
21+
"require-dev": {
22+
"phpstan/phpstan": "1.11.7"
23+
}
24+
}

e2e/discussion-11362/composer.lock

Lines changed: 103 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Repro\Site\Domain\Model;
6+
7+
class ContentPage
8+
{
9+
public const CONTENT_PAGE_TYPE_CONTENT_ELEMENTS = 'content-elements';
10+
11+
public const CONTENT_PAGE_TYPE_KARAOKE_PLAYER = 'karaoke-player';
12+
13+
protected ?Issue $parentIssue = null;
14+
15+
protected ?Lesson $parentLesson = null;
16+
17+
protected string $title;
18+
19+
protected string $type;
20+
21+
protected bool $navigationVisible;
22+
23+
protected string $navigationColor;
24+
25+
public function __construct()
26+
{
27+
}
28+
29+
public function getParentIssue(): ?Issue
30+
{
31+
return $this->parentIssue;
32+
}
33+
34+
public function getParentLesson(): ?Lesson
35+
{
36+
return $this->parentLesson;
37+
}
38+
39+
public function getTitle(): string
40+
{
41+
return $this->title;
42+
}
43+
44+
public function getType(): string
45+
{
46+
return $this->type;
47+
}
48+
49+
public function getNavigationVisible(): bool
50+
{
51+
return $this->navigationVisible;
52+
}
53+
54+
public function getNavigationColor(): string
55+
{
56+
return $this->navigationColor;
57+
}
58+
}
59+
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Repro\Site\Domain\Model;
6+
7+
use DateTime;
8+
9+
class Issue
10+
{
11+
protected array $settings;
12+
13+
protected ?SchoolYear $parentSchoolYear = null;
14+
15+
protected string $title;
16+
17+
protected int $startDate;
18+
19+
protected string $holidayTitle;
20+
21+
22+
public function __construct()
23+
{
24+
}
25+
26+
public function getParentSchoolYear(): SchoolYear
27+
{
28+
return $this->parentSchoolYear;
29+
}
30+
31+
public function getTitle(): string
32+
{
33+
return $this->title;
34+
}
35+
36+
public function getStartDate(): int
37+
{
38+
return $this->startDate;
39+
}
40+
41+
public function getHolidayTitle(): string
42+
{
43+
return $this->holidayTitle;
44+
}
45+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Repro\Site\Domain\Model;
6+
7+
class Lesson
8+
{
9+
protected ?SchoolLevel $schoolLevel = null;
10+
11+
protected ?Issue $parentIssue = null;
12+
13+
protected int $lessonNumber;
14+
15+
public function getSchoolLevel(): ?SchoolLevel
16+
{
17+
return $this->schoolLevel;
18+
}
19+
20+
public function getParentIssue(): ?Issue
21+
{
22+
return $this->parentIssue;
23+
}
24+
25+
public function getLessonNumber(): int
26+
{
27+
return $this->lessonNumber;
28+
}
29+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Repro\Site\Domain\Model;
6+
7+
class SchoolLevel
8+
{
9+
protected string $title;
10+
11+
public function getTitle(): string
12+
{
13+
return $this->title;
14+
}
15+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Repro\Site\Domain\Model;
6+
7+
class SchoolYear
8+
{
9+
protected int $startDate;
10+
11+
protected int $endDate;
12+
13+
protected int $introStartDate;
14+
15+
protected int $introEndDate;
16+
17+
public function __construct()
18+
{
19+
}
20+
21+
public function getStartDate(): int
22+
{
23+
return $this->startDate;
24+
}
25+
26+
public function getEndDate(): int
27+
{
28+
return $this->endDate;
29+
}
30+
31+
public function getIntroStartDate(): int
32+
{
33+
return $this->introStartDate;
34+
}
35+
36+
public function getIntroEndDate(): int
37+
{
38+
return $this->introEndDate;
39+
}
40+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"autoload": {
3+
"psr-4": {
4+
"Repro\\Site\\": "Classes"
5+
}
6+
},
7+
"name": "repro/site",
8+
"require": {
9+
"php": "^8.1"
10+
}
11+
}

e2e/discussion-11362/phpstan.neon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
parameters:
2+
excludePaths:
3+
analyseAndScan:
4+
- .git
5+
analyse:
6+
- vendor
7+
8+
level: 1
9+
10+
paths:
11+
- .

src/Parser/PathRoutingParser.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44

55
use PHPStan\File\FileHelper;
66
use function array_fill_keys;
7+
use function array_slice;
8+
use function count;
9+
use function explode;
10+
use function implode;
11+
use function is_link;
12+
use function realpath;
713
use function str_contains;
14+
use const DIRECTORY_SEPARATOR;
815

916
class PathRoutingParser implements Parser
1017
{
@@ -41,6 +48,24 @@ public function parseFile(string $file): array
4148

4249
$file = $this->fileHelper->normalizePath($file);
4350
if (!isset($this->analysedFiles[$file])) {
51+
// check symlinked file that still might be in analysedFiles
52+
$pathParts = explode(DIRECTORY_SEPARATOR, $file);
53+
for ($i = count($pathParts); $i > 1; $i--) {
54+
$joinedPartOfPath = implode(DIRECTORY_SEPARATOR, array_slice($pathParts, 0, $i));
55+
if (!@is_link($joinedPartOfPath)) {
56+
continue;
57+
}
58+
59+
$realFilePath = realpath($file);
60+
if ($realFilePath !== false) {
61+
$normalizedRealFilePath = $this->fileHelper->normalizePath($realFilePath);
62+
if (isset($this->analysedFiles[$normalizedRealFilePath])) {
63+
return $this->currentPhpVersionRichParser->parseFile($file);
64+
}
65+
}
66+
break;
67+
}
68+
4469
return $this->currentPhpVersionSimpleParser->parseFile($file);
4570
}
4671

0 commit comments

Comments
 (0)