Skip to content

Commit

Permalink
Refactor rule to exact file + line reporting (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomasVotruba authored May 19, 2023
1 parent 374c504 commit 97532b3
Show file tree
Hide file tree
Showing 23 changed files with 161 additions and 186 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,4 @@ parameters:
return_type: 50
param_type: 30
property_type: 70
print_suggestions: false
```
14 changes: 7 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"keywords": ["static analysis", "phpstan-extension"],
"require": {
"php": "^8.1",
"phpstan/phpstan": "^1.9.3",
"nette/utils": "^3.2 || ^4.0"
"phpstan/phpstan": "^1.10.3"
},
"require-dev": {
"phpstan/extension-installer": "^1.2",
"phpunit/phpunit": "^10.0",
"symplify/easy-coding-standard": "^11.2",
"rector/rector": "^0.15.18",
"phpstan/extension-installer": "^1.3",
"phpunit/phpunit": "^10.1",
"symplify/easy-coding-standard": "^11.3",
"rector/rector": "^0.16",
"tracy/tracy": "^2.9",
"php-parallel-lint/php-parallel-lint": "^1.3"
"php-parallel-lint/php-parallel-lint": "^1.3",
"tomasvotruba/unused-public": "^0.1.10"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 3 additions & 1 deletion config/extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ parameters:
return_type: 99
param_type: 99
property_type: 99
print_suggestions: false
# default, yet deprecated
print_suggestions: true

services:
- TomasVotruba\TypeCoverage\Formatter\TypeCoverageFormatter
- TomasVotruba\TypeCoverage\CollectorDataNormalizer

-
factory: TomasVotruba\TypeCoverage\Configuration
Expand Down
33 changes: 33 additions & 0 deletions src/CollectorDataNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace TomasVotruba\TypeCoverage;

use TomasVotruba\TypeCoverage\ValueObject\TypeCountAndMissingTypes;

final class CollectorDataNormalizer
{
/**
* @param mixed[] $collectorDataByPath
*/
public function normalize(array $collectorDataByPath): TypeCountAndMissingTypes
{
$totalCount = 0;

$missingTypeLinesByFilePath = [];

foreach ($collectorDataByPath as $filePath => $returnSeaLevelData) {
foreach ($returnSeaLevelData as $nestedData) {
$totalCount += $nestedData[0];

$missingTypeLinesByFilePath[$filePath] = array_merge(
$missingTypeLinesByFilePath[$filePath] ?? [],
$nestedData[1]
);
}
}

return new TypeCountAndMissingTypes($totalCount, $missingTypeLinesByFilePath);
}
}
22 changes: 6 additions & 16 deletions src/Collectors/ParamTypeDeclarationCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;

Expand All @@ -16,29 +15,24 @@
*/
final class ParamTypeDeclarationCollector implements Collector
{
public function __construct(
private readonly Standard $printerStandard
) {
}

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

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

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

$typedParamCount = 0;
foreach ($node->getParams() as $param) {
if ($param->variadic) {
// skip variadic
Expand All @@ -47,16 +41,12 @@ public function processNode(Node $node, Scope $scope): array
}

if ($param->type === null) {
$missingTypeLines[] = $param->getLine();
continue;
}

++$typedParamCount;
}

// missing at least 1 type
$printedClassMethod = $paramCount !== $typedParamCount ? $this->printerStandard->prettyPrint([$node]) : '';

return [$typedParamCount, $paramCount, $printedClassMethod];
return [$paramCount, $missingTypeLines];
}

private function shouldSkipFunctionLike(FunctionLike $functionLike): bool
Expand Down
19 changes: 4 additions & 15 deletions src/Collectors/PropertyTypeDeclarationCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;
use PHPStan\Node\InClassNode;
Expand All @@ -18,11 +17,6 @@
*/
final class PropertyTypeDeclarationCollector implements Collector
{
public function __construct(
private readonly Standard $printerStandard
) {
}

/**
* @return class-string<Node>
*/
Expand All @@ -33,42 +27,37 @@ public function getNodeType(): string

/**
* @param InClassNode $node
* @return array{int, int, string}
* @return mixed[]
*/
public function processNode(Node $node, Scope $scope): array
{
$printedProperties = '';

// return typed properties/all properties
$classLike = $node->getOriginalNode();

$propertyCount = count($classLike->getProperties());

$typedPropertyCount = 0;
$missingTypeLines = [];

foreach ($classLike->getProperties() as $property) {
// blocked by parent type
if ($this->isGuardedByParentClassProperty($scope, $property)) {
++$typedPropertyCount;
continue;
}

// already typed
if ($property->type instanceof Node) {
++$typedPropertyCount;
continue;
}

if ($this->isPropertyDocTyped($property)) {
++$typedPropertyCount;
continue;
}

// give useful context
$printedProperties .= PHP_EOL . PHP_EOL . $this->printerStandard->prettyPrint([$property]);
$missingTypeLines[] = $property->getLine();
}

return [$typedPropertyCount, $propertyCount, $printedProperties];
return [$propertyCount, $missingTypeLines];
}

private function isPropertyDocTyped(Property $property): bool
Expand Down
24 changes: 8 additions & 16 deletions src/Collectors/ReturnTypeDeclarationCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\PrettyPrinter\Standard;
use PHPStan\Analyser\Scope;
use PHPStan\Collectors\Collector;

Expand All @@ -15,35 +14,28 @@
*/
final class ReturnTypeDeclarationCollector implements Collector
{
public function __construct(
private readonly Standard $printerStandard
) {
}

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

/**
* @param ClassMethod $node
* @return array{int, int, string}
* @return mixed[]|null
*/
public function processNode(Node $node, Scope $scope): array
public function processNode(Node $node, Scope $scope): ?array
{
// skip magic
if ($node->isMagic()) {
return [0, 0, ''];
return null;
}

if ($node->returnType instanceof Node) {
$typedReturnCount = 1;
$printedNode = '';
} else {
$typedReturnCount = 0;
$printedNode = $this->printerStandard->prettyPrint([$node]);
$missingTypeLines = [];

if (! $node->returnType instanceof Node) {
$missingTypeLines[] = $node->getLine();
}

return [$typedReturnCount, 1, $printedNode];
return [1, $missingTypeLines];
}
}
5 changes: 0 additions & 5 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,4 @@ public function getRequiredReturnTypeLevel(): int
{
return $this->parameters['return_type'];
}

public function shouldPrintSuggestions(): bool
{
return $this->parameters['print_suggestions'];
}
}
44 changes: 25 additions & 19 deletions src/Formatter/TypeCoverageFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,49 @@

namespace TomasVotruba\TypeCoverage\Formatter;

use Nette\Utils\Strings;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;
use TomasVotruba\TypeCoverage\ValueObject\TypeCountAndMissingTypes;

final class TypeCoverageFormatter
{
/**
* @param string[] $errors
* @return string[]
* @return RuleError[]
*/
public function formatErrors(
string $message,
int $minimalLevel,
int $propertyCount,
int $typedPropertyCount,
array $errors
TypeCountAndMissingTypes $typeCountAndMissingTypes
): array {
if ($propertyCount === 0) {
if ($typeCountAndMissingTypes->getTotalCount() === 0) {
return [];
}

$propertyTypeDeclarationSeaLevel = 100 * ($typedPropertyCount / $propertyCount);
$typeCoveragePercentage = $typeCountAndMissingTypes->getCoveragePercentage();

// has the code met the minimal sea level of types?
if ($propertyTypeDeclarationSeaLevel >= $minimalLevel) {
if ($typeCoveragePercentage >= $minimalLevel) {
return [];
}

$errorMessage = sprintf($message, $propertyCount, $propertyTypeDeclarationSeaLevel, $minimalLevel);

if ($errors !== []) {
$errorMessage .= PHP_EOL . PHP_EOL;
$errorMessage .= implode(PHP_EOL . PHP_EOL, $errors);
$errorMessage .= PHP_EOL;

// keep error printable
$errorMessage = Strings::truncate($errorMessage, 8000);
$ruleErrors = [];

foreach ($typeCountAndMissingTypes->getMissingTypeLinesByFilePath() as $filePath => $lines) {
$errorMessage = sprintf(
$message,
$typeCountAndMissingTypes->getTotalCount(),
$typeCoveragePercentage,
$minimalLevel
);

foreach ($lines as $line) {
$ruleErrors[] = RuleErrorBuilder::message($errorMessage)
->file($filePath)
->line($line)
->build();
}
}

return [$errorMessage];
return $ruleErrors;
}
}
28 changes: 4 additions & 24 deletions src/Rules/ParamTypeCoverageRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPStan\Analyser\Scope;
use PHPStan\Node\CollectedDataNode;
use PHPStan\Rules\Rule;
use TomasVotruba\TypeCoverage\CollectorDataNormalizer;
use TomasVotruba\TypeCoverage\Collectors\ParamTypeDeclarationCollector;
use TomasVotruba\TypeCoverage\Configuration;
use TomasVotruba\TypeCoverage\Formatter\TypeCoverageFormatter;
Expand All @@ -27,6 +28,7 @@ final class ParamTypeCoverageRule implements Rule
public function __construct(
private readonly TypeCoverageFormatter $typeCoverageFormatter,
private readonly Configuration $configuration,
private readonly CollectorDataNormalizer $collectorDataNormalizer,
) {
}

Expand All @@ -50,34 +52,12 @@ public function processNode(Node $node, Scope $scope): array

$paramTypeDeclarationCollector = $node->get(ParamTypeDeclarationCollector::class);

$typedParamCount = 0;
$paramCount = 0;

$printedClassMethods = [];

foreach ($paramTypeDeclarationCollector as $paramSeaLevelData) {
foreach ($paramSeaLevelData as $nestedParamSeaLevelData) {
$typedParamCount += $nestedParamSeaLevelData[0];
$paramCount += $nestedParamSeaLevelData[1];

if (! $this->configuration->shouldPrintSuggestions()) {
continue;
}

/** @var string $printedClassMethod */
$printedClassMethod = $nestedParamSeaLevelData[2];
if ($printedClassMethod !== '') {
$printedClassMethods[] = trim($printedClassMethod);
}
}
}
$typeCountAndMissingTypes = $this->collectorDataNormalizer->normalize($paramTypeDeclarationCollector);

return $this->typeCoverageFormatter->formatErrors(
self::ERROR_MESSAGE,
$this->configuration->getRequiredParamTypeLevel(),
$paramCount,
$typedParamCount,
$printedClassMethods
$typeCountAndMissingTypes
);
}
}
Loading

0 comments on commit 97532b3

Please sign in to comment.