Skip to content

Commit 962c833

Browse files
ondrejmirtesclaude
andcommitted
$scope->fileDependency() on top of value dependencies
$scope->fileDependency($path) declares that the analysis of the current file depends on the contents of another file - a data file, a template, a docblock in a PHP file - that is read without PHPStan knowing about it. It is a value dependency through FileResultCacheValueExtension: the value is the hash of the file, or that the file does not exist, so creating, changing or deleting it re-analyses the files that declared it. The path is stored relative to the same directory as the other paths in the result cache. A dependency of the analysed file on itself is dropped, the file is re-analysed when it changes anyway. RequireFileExistsRule declares the paths on the scope now, and the paths from the deprecated RuleErrorBuilder::fileDependency() are declared on the scope of the rule's node. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
1 parent cc579f0 commit 962c833

39 files changed

Lines changed: 736 additions & 26 deletions

‎.github/workflows/e2e-tests.yml‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,134 @@ jobs:
507507
echo "$OUTPUT"
508508
../bashunit -a contains 'UsesLogger.php:7:Call to an undefined method ResultCacheE2EValueDependency\Mailer::log().' "$OUTPUT"
509509
../bashunit -a contains 'LoggerTrait.php (in context of class ResultCacheE2EValueDependency\UsesLoggerTrait):10:Call to an undefined method ResultCacheE2EValueDependency\Mailer::log().' "$OUTPUT"
510+
- script: |
511+
cd e2e/result-cache-file-dependency
512+
composer install
513+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
514+
echo "$OUTPUT"
515+
../bashunit -a contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-1'." "$OUTPUT"
516+
../bashunit -a contains "UsesMethod.php:10:Function ResultCacheE2EFileDependency\usesMethod() should return 'none' but returns 'method-1'." "$OUTPUT"
517+
../bashunit -a contains "UsesStaticMethod.php:10:Function ResultCacheE2EFileDependency\usesStaticMethod() should return 'none' but returns 'static-method-1'." "$OUTPUT"
518+
../bashunit -a contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT"
519+
../bashunit -a contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT"
520+
../bashunit -a contains 'UsesDeprecatedConfig.php:7:Deprecated config asks for an error.' "$OUTPUT"
521+
../bashunit -a not_contains 'Config asks for an error.' "$OUTPUT"
522+
# The paths declared with the deprecated RuleErrorBuilder::fileDependency() are declared on the
523+
# scope of the rule's node.
524+
printf 'quiet\n' > data/deprecated.txt
525+
INFO=$(../../bin/phpstan result-cache-info --json)
526+
echo "$INFO"
527+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
528+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
529+
echo "$OUTPUT"
530+
../bashunit -a not_contains 'Deprecated config asks for an error.' "$OUTPUT"
531+
# A rule declares its dependency on src/Config.php whether it reports an error or not. The file
532+
# declares no symbols, so no change of it reaches its exported nodes - any change re-analyses the
533+
# files of the rule's nodes anyway, next to the changed file itself: src/UsesConfig.php and
534+
# src/ConfigHolder.php. Not src/UsesConfigHolder.php depending on src/ConfigHolder.php - what
535+
# src/ConfigHolder.php declares did not change.
536+
printf "<?php\n\nreturn 'report';\n" > src/Config.php
537+
INFO=$(../../bin/phpstan result-cache-info --json)
538+
echo "$INFO"
539+
../bashunit -a equals '3' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
540+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
541+
echo "$OUTPUT"
542+
../bashunit -a contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT"
543+
../bashunit -a contains 'ConfigHolder.php:10:Config asks for an error.' "$OUTPUT"
544+
printf "<?php\n\n// a change that keeps the error\nreturn 'report';\n" > src/Config.php
545+
INFO=$(../../bin/phpstan result-cache-info --json)
546+
echo "$INFO"
547+
../bashunit -a equals '3' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
548+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
549+
echo "$OUTPUT"
550+
../bashunit -a contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT"
551+
# Dynamic return type extensions of a function, a method and a static method declared in
552+
# src/functions.php and src/Repository.php read a data file each. A change of one re-analyses the
553+
# file with the call - not the one declaring the function or the method.
554+
printf 'function-2\n' > data/function.txt
555+
INFO=$(../../bin/phpstan result-cache-info --json)
556+
echo "$INFO"
557+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
558+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
559+
echo "$OUTPUT"
560+
../bashunit -a contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-2'." "$OUTPUT"
561+
printf 'method-2\n' > data/method.txt
562+
INFO=$(../../bin/phpstan result-cache-info --json)
563+
echo "$INFO"
564+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
565+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
566+
echo "$OUTPUT"
567+
../bashunit -a contains "UsesMethod.php:10:Function ResultCacheE2EFileDependency\usesMethod() should return 'none' but returns 'method-2'." "$OUTPUT"
568+
printf 'static-method-2\n' > data/static-method.txt
569+
INFO=$(../../bin/phpstan result-cache-info --json)
570+
echo "$INFO"
571+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
572+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
573+
echo "$OUTPUT"
574+
../bashunit -a contains "UsesStaticMethod.php:10:Function ResultCacheE2EFileDependency\usesStaticMethod() should return 'none' but returns 'static-method-2'." "$OUTPUT"
575+
# Scope::getFile() is not the file to re-analyse when the extension runs outside the walk of the
576+
# analysed file: the type of Holder::$value is inferred from the constructor on a scope of
577+
# src/Holder.php, whenever the property is first needed - in the analysis of src/Holder.php, or of
578+
# src/UsesHolder.php reading it - and it is cached. So it is what src/Holder.php declares that
579+
# depends on data/holder.txt: a change of it re-analyses src/Holder.php and the files depending on it.
580+
printf 'int\n' > data/holder.txt
581+
INFO=$(../../bin/phpstan result-cache-info --json)
582+
echo "$INFO"
583+
../bashunit -a equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
584+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
585+
echo "$OUTPUT"
586+
../bashunit -a not_contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT"
587+
../bashunit -a not_contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT"
588+
# Deleting a declared file re-analyses the files depending on it too, and it stays watched while
589+
# it is missing: creating it again re-analyses them again.
590+
rm data/function.txt
591+
INFO=$(../../bin/phpstan result-cache-info --json)
592+
echo "$INFO"
593+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
594+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
595+
echo "$OUTPUT"
596+
../bashunit -a contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'missing'." "$OUTPUT"
597+
printf 'function-3\n' > data/function.txt
598+
INFO=$(../../bin/phpstan result-cache-info --json)
599+
echo "$INFO"
600+
../bashunit -a equals '1' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
601+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
602+
echo "$OUTPUT"
603+
../bashunit -a contains "UsesFunction.php:10:Function ResultCacheE2EFileDependency\usesFunction() should return 'none' but returns 'function-3'." "$OUTPUT"
604+
# The same for what src/Holder.php declares.
605+
rm data/holder.txt
606+
INFO=$(../../bin/phpstan result-cache-info --json)
607+
echo "$INFO"
608+
../bashunit -a equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
609+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
610+
echo "$OUTPUT"
611+
../bashunit -a contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT"
612+
../bashunit -a contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT"
613+
printf 'int\n' > data/holder.txt
614+
INFO=$(../../bin/phpstan result-cache-info --json)
615+
echo "$INFO"
616+
../bashunit -a equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
617+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
618+
echo "$OUTPUT"
619+
../bashunit -a not_contains 'Holder.php:17:Method ResultCacheE2EFileDependency\Holder::get() should return int but returns string.' "$OUTPUT"
620+
../bashunit -a not_contains 'UsesHolder.php:9:Function ResultCacheE2EFileDependency\usesHolder() should return int but returns string.' "$OUTPUT"
621+
# And for a declared file that is analysed itself: deleted, it is no longer analysed, and the files
622+
# of the rule's nodes are re-analysed; created again, all three are - and as with any new file, so
623+
# are the three files with errors.
624+
rm src/Config.php
625+
INFO=$(../../bin/phpstan result-cache-info --json)
626+
echo "$INFO"
627+
../bashunit -a equals '2' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
628+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
629+
echo "$OUTPUT"
630+
../bashunit -a not_contains 'Config asks for an error.' "$OUTPUT"
631+
printf "<?php\n\nreturn 'report';\n" > src/Config.php
632+
INFO=$(../../bin/phpstan result-cache-info --json)
633+
echo "$INFO"
634+
../bashunit -a equals '6' "$(echo "$INFO" | jq --raw-output '.filesToAnalyseCount')"
635+
OUTPUT=$(../bashunit -a exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
636+
echo "$OUTPUT"
637+
../bashunit -a contains 'UsesConfig.php:7:Config asks for an error.' "$OUTPUT"
510638
- script: |
511639
cd e2e/result-cache-scanned-trait
512640
../../bin/phpstan analyse
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"autoload-dev": {
3+
"classmap": ["extension/"]
4+
}
5+
}

‎e2e/result-cache-file-dependency/composer.lock‎

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
report
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
function-1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
text
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
method-1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
static-method-1
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace ResultCacheE2EFileDependency;
4+
5+
use PhpParser\Node;
6+
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Name;
8+
use PHPStan\Analyser\DependencyEmitter;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Rules\Rule;
11+
use PHPStan\Rules\RuleErrorBuilder;
12+
13+
/**
14+
* Reports calls to checkConfig() when src/Config.php asks for it. The file is read whether an
15+
* error is reported or not, so the dependency is declared either way.
16+
*
17+
* @implements Rule<FuncCall>
18+
*/
19+
final class ConfigRule implements Rule
20+
{
21+
22+
public function getNodeType(): string
23+
{
24+
return FuncCall::class;
25+
}
26+
27+
/**
28+
* @param Scope&DependencyEmitter $scope
29+
*/
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
if (!$node->name instanceof Name || $node->name->getLast() !== 'checkConfig') {
33+
return [];
34+
}
35+
36+
$configFile = dirname(__DIR__) . '/src/Config.php';
37+
$scope->fileDependency($configFile);
38+
39+
if (!is_file($configFile)) {
40+
return [];
41+
}
42+
43+
$contents = file_get_contents($configFile);
44+
if ($contents === false || !str_contains($contents, 'report')) {
45+
return [];
46+
}
47+
48+
return [
49+
RuleErrorBuilder::message('Config asks for an error.')->identifier('resultCacheE2E.config')->build(),
50+
];
51+
}
52+
53+
}

0 commit comments

Comments
 (0)