Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,64 @@ jobs:
../bashunit -a contains 'Result cache not used because extension file' "$OUTPUT"
../bashunit -a contains 'the rule says another thing' "$OUTPUT"
../bashunit -a not_contains 'the rule says one thing' "$OUTPUT"
- script: |
cd e2e/result-cache-rule-edit
composer install
# Composer loads the rules and the collector, which are the project's own code, and the project
# turns on resultCacheRuleScopedReanalysis. A run follows each edit below. The expected lines
# come from a cold run of the same files.
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'A.php:8:R: foo v1' "$OUTPUT"
../bashunit -a contains 'A.php:10:No error with identifier r.foo is reported on line 10.' "$OUTPUT"
../bashunit -a contains 'C.php:1:Called v1: foo' "$OUTPUT"
# The rule changes its messages. The trait error and the error that R reports into
# Other.php follow the edit. A cold run reports the unmatched ignore on that line too.
patch -p0 < rule-message.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Changed rule: ' "$OUTPUT"
../bashunit -a not_contains 'Result cache not used' "$OUTPUT"
../bashunit -a contains 'A.php:8:R: foo v2' "$OUTPUT"
../bashunit -a contains 'T.php (in context of class ResultCacheE2ERuleEdit\C):9:R: foo v2' "$OUTPUT"
../bashunit -a contains 'Other.php:3:R: reported in Other.php v2' "$OUTPUT"
../bashunit -a contains 'Other.php:3:No error with identifier r.elsewhere is reported on line 3.' "$OUTPUT"
../bashunit -a not_contains 'v1 [identifier=r.' "$OUTPUT"
# The rule stops reporting foo(), so the ignore comment on A.php line 7 matches nothing.
patch -p0 < rule-stops-foo.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Changed rule: ' "$OUTPUT"
../bashunit -a not_contains 'Result cache not used' "$OUTPUT"
../bashunit -a contains 'A.php:7:No error with identifier r.foo is reported on line 7.' "$OUTPUT"
../bashunit -a not_contains 'R: foo' "$OUTPUT"
# The rule reports foo() again, and strlen() under the same identifier. The ignore comment
# on A.php line 10 now matches, so its unmatched-ignore error goes away.
patch -p0 < rule-reports-strlen.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Changed rule: ' "$OUTPUT"
../bashunit -a not_contains 'Result cache not used' "$OUTPUT"
../bashunit -a contains 'A.php:8:R: foo v3' "$OUTPUT"
../bashunit -a contains 'T.php (in context of class ResultCacheE2ERuleEdit\C):9:R: foo v3' "$OUTPUT"
../bashunit -a contains 'Other.php:3:R: reported in Other.php v3' "$OUTPUT"
../bashunit -a not_contains 'No error with identifier r.foo' "$OUTPUT"
# The CollectedDataNode rule changes. It runs on the collected data, not on files.
patch -p0 < collected-data-rule.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Re-running it on 0 files' "$OUTPUT"
../bashunit -a not_contains 'Result cache not used' "$OUTPUT"
../bashunit -a contains 'A.php:1:Called v2: foo' "$OUTPUT"
../bashunit -a contains 'C.php:1:Called v2: foo' "$OUTPUT"
../bashunit -a not_contains 'Called v1' "$OUTPUT"
# A changed collector can change the collected data of every file, so PHPStan analyses all files again.
patch -p0 < collector.patch
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit -a contains 'Result cache not used because extension file' "$OUTPUT"
../bashunit -a contains 'A.php:1:Called v2: foo' "$OUTPUT"
../bashunit -a not_contains 'C.php:1:Called' "$OUTPUT"
- script: |
cd e2e/result-cache-extension-path-package-update
composer install
Expand Down
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ parameters:
resultCachePath: %tmpDir%/resultCache.php
resultCacheSkipIfOlderThanDays: 7
resultCacheChecksProjectExtensionFilesDependencies: false
resultCacheRuleScopedReanalysis: false
dynamicConstantNames:
- ICONV_IMPL
- LIBXML_VERSION
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ parametersSchema:
resultCachePath: string()
resultCacheSkipIfOlderThanDays: int()
resultCacheChecksProjectExtensionFilesDependencies: bool()
resultCacheRuleScopedReanalysis: bool()
dynamicConstantNames: arrayOf(string())
customRulesetUsed: schema(bool(), nullable())
rootDir: string()
Expand Down
2 changes: 2 additions & 0 deletions e2e/result-cache-rule-edit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/vendor
/composer.lock
11 changes: 11 additions & 0 deletions e2e/result-cache-rule-edit/collected-data-rule.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--- rules/CalledRule.php
+++ rules/CalledRule.php
@@ -27,7 +27,7 @@
if ($name !== 'foo') {
continue;
}
- $errors[] = RuleErrorBuilder::message('Called v1: ' . $name)->identifier('called.foo')->file($file)->line(1)->build();
+ $errors[] = RuleErrorBuilder::message('Called v2: ' . $name)->identifier('called.foo')->file($file)->line(1)->build();
}
}

13 changes: 13 additions & 0 deletions e2e/result-cache-rule-edit/collector.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--- rules/CalledCollector.php
+++ rules/CalledCollector.php
@@ -21,6 +21,10 @@

public function processNode(Node $node, Scope $scope)
{
+ if ($scope->isInTrait()) {
+ return null;
+ }
+
return $node->name instanceof Name ? $node->name->toString() : null;
}

8 changes: 8 additions & 0 deletions e2e/result-cache-rule-edit/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "phpstan/result-cache-rule-edit-e2e",
"autoload": {
"psr-4": {
"ResultCacheE2ERuleEdit\\": "rules/"
}
}
}
17 changes: 17 additions & 0 deletions e2e/result-cache-rule-edit/phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
parameters:
level: 0
tmpDir: tmp
resultCacheRuleScopedReanalysis: true
paths:
- src
- rules

rules:
- ResultCacheE2ERuleEdit\R
- ResultCacheE2ERuleEdit\CalledRule

services:
-
class: ResultCacheE2ERuleEdit\CalledCollector
tags:
- phpstan.collector
15 changes: 15 additions & 0 deletions e2e/result-cache-rule-edit/rule-message.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--- rules/R.php
+++ rules/R.php
@@ -28,10 +28,10 @@

$name = $node->name->toString();
if ($name === 'foo') {
- return [RuleErrorBuilder::message('R: foo v1')->identifier('r.foo')->build()];
+ return [RuleErrorBuilder::message('R: foo v2')->identifier('r.foo')->build()];
}
if ($name === 'elsewhere') {
- return [RuleErrorBuilder::message('R: reported in Other.php v1')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
+ return [RuleErrorBuilder::message('R: reported in Other.php v2')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
}

return [];
18 changes: 18 additions & 0 deletions e2e/result-cache-rule-edit/rule-reports-strlen.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--- rules/R.php
+++ rules/R.php
@@ -27,8 +27,14 @@
}

$name = $node->name->toString();
+ if ($name === 'foo') {
+ return [RuleErrorBuilder::message('R: foo v3')->identifier('r.foo')->build()];
+ }
+ if ($name === 'strlen') {
+ return [RuleErrorBuilder::message('R: strlen v3')->identifier('r.foo')->build()];
+ }
if ($name === 'elsewhere') {
- return [RuleErrorBuilder::message('R: reported in Other.php v2')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
+ return [RuleErrorBuilder::message('R: reported in Other.php v3')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
}

return [];
12 changes: 12 additions & 0 deletions e2e/result-cache-rule-edit/rule-stops-foo.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- rules/R.php
+++ rules/R.php
@@ -27,9 +27,6 @@
}

$name = $node->name->toString();
- if ($name === 'foo') {
- return [RuleErrorBuilder::message('R: foo v2')->identifier('r.foo')->build()];
- }
if ($name === 'elsewhere') {
return [RuleErrorBuilder::message('R: reported in Other.php v2')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
}
27 changes: 27 additions & 0 deletions e2e/result-cache-rule-edit/rules/CalledCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2ERuleEdit;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;

/**
* @implements Collector<FuncCall, string>
*/
final class CalledCollector implements Collector
{

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope)
{
return $node->name instanceof Name ? $node->name->toString() : null;
}

}
37 changes: 37 additions & 0 deletions e2e/result-cache-rule-edit/rules/CalledRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2ERuleEdit;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<CollectedDataNode>
*/
final class CalledRule implements Rule
{

public function getNodeType(): string
{
return CollectedDataNode::class;
}

public function processNode(Node $node, Scope $scope): array
{
$errors = [];
foreach ($node->get(CalledCollector::class) as $file => $names) {
foreach ($names as $name) {
if ($name !== 'foo') {
continue;
}
$errors[] = RuleErrorBuilder::message('Called v1: ' . $name)->identifier('called.foo')->file($file)->line(1)->build();
}
}

return $errors;
}

}
40 changes: 40 additions & 0 deletions e2e/result-cache-rule-edit/rules/R.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php declare(strict_types = 1);

namespace ResultCacheE2ERuleEdit;

use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @implements Rule<FuncCall>
*/
final class R implements Rule
{

public function getNodeType(): string
{
return FuncCall::class;
}

public function processNode(Node $node, Scope $scope): array
{
if (!$node->name instanceof Name) {
return [];
}

$name = $node->name->toString();
if ($name === 'foo') {
return [RuleErrorBuilder::message('R: foo v1')->identifier('r.foo')->build()];
}
if ($name === 'elsewhere') {
return [RuleErrorBuilder::message('R: reported in Other.php v1')->identifier('r.elsewhere')->file(realpath(__DIR__ . '/../src/Other.php'))->line(3)->build()];
}

return [];
}

}
12 changes: 12 additions & 0 deletions e2e/result-cache-rule-edit/src/A.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace ResultCacheE2ERuleEdit;

function a(): void
{
foo(); // @phpstan-ignore r.foo
foo();
undefinedOne(); // @phpstan-ignore function.notFound
strlen('x'); // @phpstan-ignore r.foo
elsewhere();
}
8 changes: 8 additions & 0 deletions e2e/result-cache-rule-edit/src/C.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace ResultCacheE2ERuleEdit;

class C
{
use T;
}
8 changes: 8 additions & 0 deletions e2e/result-cache-rule-edit/src/NoCalls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace ResultCacheE2ERuleEdit;

class NoCalls
{
public int $x = 1;
}
8 changes: 8 additions & 0 deletions e2e/result-cache-rule-edit/src/Other.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace ResultCacheE2ERuleEdit; // @phpstan-ignore r.elsewhere

function other(): void
{
undefinedTwo();
}
12 changes: 12 additions & 0 deletions e2e/result-cache-rule-edit/src/T.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace ResultCacheE2ERuleEdit;

trait T
{
public function t(): void
{
foo();
foo(); // @phpstan-ignore r.foo
}
}
2 changes: 2 additions & 0 deletions e2e/result-cache-rule-edit/tmp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.*
3 changes: 3 additions & 0 deletions src/Analyser/Analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public function analyse(
$valueDependencies = ['values' => [], 'dependents' => []];
$packageDependencies = [];
$exportedNodes = [];
$nodeTypes = [];
$allProcessedFiles = [];
foreach ($files as $file) {
if ($preFileCallback !== null) {
Expand Down Expand Up @@ -104,6 +105,7 @@ public function analyse(
$usedTraitDependencies[$file] = $fileAnalyserResult->getUsedTraitDependencies();
$valueDependencies = ValueDependencyCollector::merge($valueDependencies, $fileAnalyserResult->getValueDependencies());
$packageDependencies[$file] = $fileAnalyserResult->getPackageDependencies();
$nodeTypes[$file] = $fileAnalyserResult->getNodeTypes();

$fileExportedNodes = $fileAnalyserResult->getExportedNodes();
if (count($fileExportedNodes) > 0) {
Expand Down Expand Up @@ -152,6 +154,7 @@ public function analyse(
// analysis ran in this process - its peak is read where the number is printed
peakMemoryUsageBytes: 0,
processedFiles: $allProcessedFiles,
nodeTypes: $nodeTypes,
);
}

Expand Down
10 changes: 10 additions & 0 deletions src/Analyser/AnalyserResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ final class AnalyserResult
* @param array<string, array<string>>|null $packageDependencies
* @param array<string, array<RootExportedNode>> $exportedNodes
* @param list<string> $processedFiles
* @param array<string, list<string>> $nodeTypes the node classes each analysed file passed to the rules
*/
public function __construct(
private array $unorderedErrors,
Expand All @@ -51,10 +52,19 @@ public function __construct(
private int $peakMemoryUsageBytes,
private array $processedFiles,
private int $workerCount = 0,
private array $nodeTypes = [],
)
{
}

/**
* @return array<string, list<string>>
*/
public function getNodeTypes(): array
{
return $this->nodeTypes;
}

/**
* How many parallel workers produced this result; 0 when the analysis ran in
* the main process.
Expand Down
Loading
Loading