Skip to content
Merged
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
2 changes: 1 addition & 1 deletion tools/.phpstan/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"phpstan/phpstan": "^2.1.56",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan-strict-rules": "^2.0.11",
"tomasvotruba/type-coverage": "^2.1.0",
"tomasvotruba/type-coverage": "^2.2.1",
"ergebnis/phpstan-rules": "^2.13.1"
},
"config": {
Expand Down
16 changes: 8 additions & 8 deletions tools/.phpstan/composer.lock

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

16 changes: 8 additions & 8 deletions tools/.phpstan/vendor/composer/installed.json
Original file line number Diff line number Diff line change
Expand Up @@ -333,25 +333,25 @@
},
{
"name": "tomasvotruba/type-coverage",
"version": "2.1.0",
"version_normalized": "2.1.0.0",
"version": "2.2.1",
"version_normalized": "2.2.1.0",
"source": {
"type": "git",
"url": "https://github.com/TomasVotruba/type-coverage.git",
"reference": "468354b3964120806a69890cbeb3fcf005876391"
"reference": "4087caa4639bdd4c646f2984bc333efeddf69e4b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/468354b3964120806a69890cbeb3fcf005876391",
"reference": "468354b3964120806a69890cbeb3fcf005876391",
"url": "https://api.github.com/repos/TomasVotruba/type-coverage/zipball/4087caa4639bdd4c646f2984bc333efeddf69e4b",
"reference": "4087caa4639bdd4c646f2984bc333efeddf69e4b",
"shasum": ""
},
"require": {
"nette/utils": "^3.2 || ^4.0",
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0"
"phpstan/phpstan": "^2.1.33"
},
"time": "2025-12-05T16:38:02+00:00",
"time": "2026-05-26T08:14:01+00:00",
"type": "phpstan-extension",
"extra": {
"phpstan": {
Expand All @@ -377,7 +377,7 @@
],
"support": {
"issues": "https://github.com/TomasVotruba/type-coverage/issues",
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.1.0"
"source": "https://github.com/TomasVotruba/type-coverage/tree/2.2.1"
},
"funding": [
{
Expand Down
10 changes: 5 additions & 5 deletions tools/.phpstan/vendor/composer/installed.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '65393074e5437e41b6f8d27247c19230c8ff02c7',
'reference' => '0398c8400327a363191c3004854396b29281256e',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
Expand All @@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '65393074e5437e41b6f8d27247c19230c8ff02c7',
'reference' => '0398c8400327a363191c3004854396b29281256e',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
Expand Down Expand Up @@ -65,9 +65,9 @@
'dev_requirement' => true,
),
'tomasvotruba/type-coverage' => array(
'pretty_version' => '2.1.0',
'version' => '2.1.0.0',
'reference' => '468354b3964120806a69890cbeb3fcf005876391',
'pretty_version' => '2.2.1',
'version' => '2.2.1.0',
'reference' => '4087caa4639bdd4c646f2984bc333efeddf69e4b',
'type' => 'phpstan-extension',
'install_path' => __DIR__ . '/../tomasvotruba/type-coverage',
'aliases' => array(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"keywords": ["static analysis", "phpstan-extension"],
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan": "^2.1.33",
"nette/utils": "^3.2 || ^4.0"
},
"autoload": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
final class CollectorDataNormalizer
{
/**
* @param array<string, array<array{0: int, 1: array<string, int>}>> $collectorDataByPath
* @param array<string, array<array{0: int, 1: array<int, int>, 2?: string|null}>> $collectorDataByPath
*/
public function normalize(array $collectorDataByPath): TypeCountAndMissingTypes
{
Expand All @@ -24,8 +24,12 @@ public function normalize(array $collectorDataByPath): TypeCountAndMissingTypes

$missingCount += count($nestedData[1]);

$missingTypeLinesByFilePath[$filePath] = array_merge(
$missingTypeLinesByFilePath[$filePath] ?? [],
// if the node is from a trait, route the error to the trait file
// instead of the using-class file, so lines match the actual source
$effectiveFilePath = $nestedData[2] ?? $filePath;

$missingTypeLinesByFilePath[$effectiveFilePath] = array_merge(
$missingTypeLinesByFilePath[$effectiveFilePath] ?? [],
$nestedData[1]
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ public function getNodeType(): string

/**
* @param ClassConstantsNode $node
* @return array<int, int|list<(int | int<1, max>)>>
* @return array{int, list<int>, string|null}
*/
public function processNode(Node $node, Scope $scope): array
{
// enable only on PHP 8.3+
if (PHP_VERSION_ID < 80300) {
return [0, []];
return [0, [], null];
}

$constantCount = count($node->getConstants());
Expand All @@ -54,7 +54,13 @@ public function processNode(Node $node, Scope $scope): array
$missingTypeLines[] = $classConst->getLine();
}

return [$constantCount, $missingTypeLines];
$traitFilePath = null;
if ($scope->isInTrait()) {
$traitFilePath = $scope->getTraitReflection()
->getFileName();
}

return [$constantCount, $missingTypeLines, $traitFilePath];
}

private function isGuardedByParentClassConstant(Scope $scope, ClassConst $classConst): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;
use PHPStan\Reflection\ClassReflection;

/**
* @see \TomasVotruba\TypeCoverage\Rules\ParamTypeCoverageRule
Expand All @@ -22,14 +24,19 @@ public function getNodeType(): string

/**
* @param FunctionLike $node
* @return mixed[]|null
* @return array{int, list<int>, string|null}|null
*/
public function processNode(Node $node, Scope $scope): ?array
{
if ($this->shouldSkipFunctionLike($node)) {
return null;
}

// skip methods inherited from a parent class or interface, as types are locked by LSP
if ($node instanceof ClassMethod && $this->isGuardedByParentMethod($scope, $node)) {
return null;
}

$missingTypeLines = [];
$paramCount = count($node->getParams());

Expand All @@ -45,7 +52,17 @@ public function processNode(Node $node, Scope $scope): ?array
}
}

return [$paramCount, $missingTypeLines];
return [$paramCount, $missingTypeLines, $this->resolveTraitFilePath($scope)];
}

private function resolveTraitFilePath(Scope $scope): ?string
{
if (! $scope->isInTrait()) {
return null;
}

return $scope->getTraitReflection()
->getFileName();
}

private function shouldSkipFunctionLike(FunctionLike $functionLike): bool
Expand All @@ -58,6 +75,30 @@ private function shouldSkipFunctionLike(FunctionLike $functionLike): bool
return $this->hasFunctionLikeCallableParam($functionLike);
}

private function isGuardedByParentMethod(Scope $scope, ClassMethod $classMethod): bool
{
$classReflection = $scope->getClassReflection();
if (! $classReflection instanceof ClassReflection) {
return false;
}

$methodName = $classMethod->name->toString();

foreach ($classReflection->getParents() as $parentClassReflection) {
if ($parentClassReflection->hasMethod($methodName)) {
return true;
}
}

foreach ($classReflection->getInterfaces() as $interfaceReflection) {
if ($interfaceReflection->hasMethod($methodName)) {
return true;
}
}

return false;
}

private function hasFunctionLikeCallableParam(FunctionLike $functionLike): bool
{
// skip callable, can be anythings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function getNodeType(): string

/**
* @param InClassNode $node
* @return array<int, int|list<(int | int<1, max>)>>
* @return array{int, list<int>, string|null}
*/
public function processNode(Node $node, Scope $scope): array
{
Expand Down Expand Up @@ -57,7 +57,13 @@ public function processNode(Node $node, Scope $scope): array
$missingTypeLines[] = $property->getLine();
}

return [$propertyCount, $missingTypeLines];
$traitFilePath = null;
if ($scope->isInTrait()) {
$traitFilePath = $scope->getTraitReflection()
->getFileName();
}

return [$propertyCount, $missingTypeLines, $traitFilePath];
}

private function isPropertyDocTyped(Property $property): bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function getNodeType(): string

/**
* @param ClassMethod $node
* @return mixed[]|null
* @return array{int, list<int>, string|null}|null
*/
public function processNode(Node $node, Scope $scope): ?array
{
Expand All @@ -30,11 +30,15 @@ public function processNode(Node $node, Scope $scope): ?array
return null;
}

$traitFilePath = null;
if ($scope->isInTrait()) {
$originalMethodName = $node->getAttribute('originalTraitMethodName');
if ($originalMethodName === '__construct') {
return null;
}

$traitFilePath = $scope->getTraitReflection()
->getFileName();
}

$missingTypeLines = [];
Expand All @@ -43,6 +47,6 @@ public function processNode(Node $node, Scope $scope): ?array
$missingTypeLines[] = $node->getLine();
}

return [1, $missingTypeLines];
return [1, $missingTypeLines, $traitFilePath];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ final class DeclareCoverageRule implements Rule
*/
public const ERROR_MESSAGE = 'Out of %d possible declare(strict_types=1), only %d - %.1f %% actually have it. Add more declares to get over %s %%';

/**
* @var string
*/
private const IDENTIFIER = 'typeCoverage.declareCoverage';

/**
* @readonly
*/
Expand Down Expand Up @@ -108,7 +113,10 @@ public function processNode(Node $node, Scope $scope): array
$requiredDeclareLevel,
);

$ruleErrors[] = RuleErrorBuilder::message($errorMessage)->file($notCoveredDeclareFilePath)->build();
$ruleErrors[] = RuleErrorBuilder::message($errorMessage)
->identifier(self::IDENTIFIER)
->file($notCoveredDeclareFilePath)
->build();
}

return $ruleErrors;
Expand Down
Loading