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
57 changes: 57 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,63 @@ jobs:
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan -vvv")
echo "$OUTPUT"
../bashunit assert contains 'Result cache restored.' "$OUTPUT"
- script: |
cd e2e/result-cache-collected-data-rules
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT"
# Nothing changed, so every rule on CollectedDataNode reuses its errors from the result cache.
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT"
echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.'
../bashunit assert contains 'Result cache was not rewritten because it is unchanged.' "$OUTPUT"
# src/Other.php is analysed again, but what it collects stays the same.
echo '// changed' >> src/Other.php
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Result cache restored. 1 file will be reanalysed.' "$OUTPUT"
../bashunit assert contains 'Caller.php:10:Call to function ResultCacheCollectedDataRules\pure() on a separate line has no effect.' "$OUTPUT"
echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.'
# Removing the call changes what src/Caller.php collects, so the rule runs again and the error goes away.
sed -i 's/^\t\tpure();$//' src/Caller.php
OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert not_contains 'on a separate line has no effect' "$OUTPUT"
- script: |
cd e2e/result-cache-collected-data-rules-tracking
composer install
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Controller.php:15:View about not found.' "$OUTPUT"
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
echo "$OUTPUT" | grep -E 'Result cache reused the errors of ([0-9]+) of \1 rules on collected data\.'
# The rule tracks the views directory, so a new view runs it again.
printf 'about\n' > views/about.txt
OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert not_contains 'View about not found.' "$OUTPUT"
rm views/about.txt
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Controller.php:15:View about not found.' "$OUTPUT"
# The rule tracks the file of ignored views too.
printf 'about\n' > ignored-views.txt
OUTPUT=$(../bashunit assert exit_code "0" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert not_contains 'View about not found.' "$OUTPUT"
# And the class of extra views, which is not analysed.
sed -i "s/'contact'/'nothing'/" lib/ExtraViews.php
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Controller.php:20:View contact not found.' "$OUTPUT"
# And a method that a class reflection extension describes from a file that is not
# analysed, tracked through DeclarationDependencyTracker. No analysed file changes.
printf '' > more-views.txt
OUTPUT=$(../bashunit assert exit_code "1" "../../bin/phpstan analyse -vv --error-format raw")
echo "$OUTPUT"
../bashunit assert contains 'Controller.php:25:View faq not found.' "$OUTPUT"
- script: |
cd e2e/bug-14514
composer install
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ e2e/bashunit
/e2e/result-cache-moved-tmpdir/tmp-a
/e2e/result-cache-moved-tmpdir/tmp-b
/e2e/result-cache-stale-objects/tmp
/e2e/result-cache-collected-data-rules/tmp
/e2e/result-cache-collected-data-rules-tracking/tmp
/e2e/result-cache-truncated/tmp
/e2e/result-cache-worker-count/tmp
e2e/restart-manager/probe.log
Expand Down
1 change: 1 addition & 0 deletions conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ parameters:
backedEnumFromParameterType: true
unusedVariable: true
unusedParameters: true
reuseCollectedDataRuleErrors: true
1 change: 1 addition & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ parameters:
backedEnumFromParameterType: false
unusedVariable: false
unusedParameters: false
reuseCollectedDataRuleErrors: false
fileExtensions:
- php
checkAdvancedIsset: false
Expand Down
1 change: 1 addition & 0 deletions conf/parametersSchema.neon
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ parametersSchema:
backedEnumFromParameterType: bool()
unusedVariable: bool()
unusedParameters: bool()
reuseCollectedDataRuleErrors: bool()
])
fileExtensions: listOf(string())
checkAdvancedIsset: bool()
Expand Down
1 change: 1 addition & 0 deletions e2e/result-cache-collected-data-rules-tracking/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/vendor
5 changes: 5 additions & 0 deletions e2e/result-cache-collected-data-rules-tracking/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"autoload-dev": {
"classmap": ["extension/"]
}
}
18 changes: 18 additions & 0 deletions e2e/result-cache-collected-data-rules-tracking/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

use PHPStan\Analyser\DeclarationDependencyTracker;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
use PHPStan\Type\StringType;
use function file;
use function in_array;

/**
* MoreViews has a method for each view listed in a file that is not analysed. A class reflection
* extension gets no Scope, so it tracks the file on the class.
*/
final class MoreViewsExtension implements MethodsClassReflectionExtension
{

public function __construct(
private DeclarationDependencyTracker $declarationDependencyTracker,
private string $moreViewsFile,
)
{
}

public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
if ($classReflection->getName() !== MoreViews::class) {
return false;
}

$this->declarationDependencyTracker->trackFileDependency($classReflection, $this->moreViewsFile);

return in_array($methodName, file($this->moreViewsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES), true);
}

public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection
{
return new ViewMethodReflection($classReflection, $methodName, new StringType());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

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

/**
* @implements Collector<FuncCall, array{string, int}>
*/
final class ViewCollector implements Collector
{

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

public function processNode(Node $node, Scope $scope)
{
if (!$node->name instanceof Name || $node->name->toLowerString() !== 'view') {
return null;
}

$args = $node->getArgs();
if (!isset($args[0]) || !$args[0]->value instanceof String_) {
return null;
}

return [$args[0]->value->value, $node->getStartLine()];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\FunctionVariant;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Type;

final class ViewMethodReflection implements MethodReflection
{

public function __construct(private ClassReflection $declaringClass, private string $name, private Type $returnType)
{
}

public function getDeclaringClass(): ClassReflection
{
return $this->declaringClass;
}

public function isStatic(): bool
{
return false;
}

public function isPrivate(): bool
{
return false;
}

public function isPublic(): bool
{
return true;
}

public function getDocComment(): ?string
{
return null;
}

public function getName(): string
{
return $this->name;
}

public function getPrototype(): ClassMemberReflection
{
return $this;
}

public function getVariants(): array
{
return [new FunctionVariant(TemplateTypeMap::createEmpty(), null, [], false, $this->returnType)];
}

public function isDeprecated(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

public function getDeprecatedDescription(): ?string
{
return null;
}

public function isFinal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

public function isInternal(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

public function getThrowType(): ?Type
{
return null;
}

public function hasSideEffects(): TrinaryLogic
{
return TrinaryLogic::createNo();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

use PhpParser\Node;
use PHPStan\Analyser\DependencyTracker;
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function file;
use function in_array;
use function is_file;
use function sprintf;

/**
* Reports a view without a file in the views directory, unless the view is listed in a file of
* ignored views or is a constant of ExtraViews. None of them is analysed, so the rule declares
* all three. A view can also be a method of MoreViews, which MoreViewsExtension describes and
* declares itself.
*
* @implements Rule<CollectedDataNode>
*/
final class ViewRule implements Rule
{

public function __construct(
private ReflectionProvider $reflectionProvider,
private string $viewsDirectory,
private string $ignoredViewsFile,
)
{
}

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

/**
* @param Scope&DependencyTracker $scope
*/
public function processNode(Node $node, Scope $scope): array
{
$scope->trackDirectoryDependency($this->viewsDirectory, '*.txt');
$scope->trackFileDependency($this->ignoredViewsFile);
$scope->trackClassDependency(ExtraViews::class);

$ignoredViews = file($this->ignoredViewsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$extraViews = $this->reflectionProvider->getClass(ExtraViews::class)->getNativeReflection()->getConstants();
$moreViews = $this->reflectionProvider->getClass(MoreViews::class);
$errors = [];
foreach ($node->get(ViewCollector::class) as $file => $views) {
foreach ($views as [$view, $line]) {
if (is_file($this->viewsDirectory . '/' . $view . '.txt') || in_array($view, $ignoredViews, true) || in_array($view, $extraViews, true) || $moreViews->hasMethod($view)) {
continue;
}

$errors[] = RuleErrorBuilder::message(sprintf('View %s not found.', $view))
->file($file)
->line($line)
->identifier('view.notFound')
->build();
}
}

return $errors;
}

}
Empty file.
10 changes: 10 additions & 0 deletions e2e/result-cache-collected-data-rules-tracking/lib/ExtraViews.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

class ExtraViews
{

public const CONTACT = 'contact';

}
11 changes: 11 additions & 0 deletions e2e/result-cache-collected-data-rules-tracking/lib/MoreViews.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php declare(strict_types = 1);

namespace ResultCacheCollectedDataRulesTracking;

/**
* Has a method for each view in more-views.txt, from MoreViewsExtension.
*/
class MoreViews
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
faq
Loading
Loading