Skip to content

Commit

Permalink
Merge pull request #3 from yamadashy/feature/error-identifier-grouping
Browse files Browse the repository at this point in the history
feat: Add Error Identifier Summary and improve formatting
  • Loading branch information
yamadashy authored Sep 21, 2024
2 parents fa762fb + c49b45a commit 1f0809b
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 177 deletions.
20 changes: 20 additions & 0 deletions .github/renovate.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly",
'group:allNonMajor'
],
"rangeStrategy": "bump",
"dependencyDashboard": false,
"labels": ["dependencies", "renovate"],
"packageRules": [
{
matchDepTypes: ['peerDependencies'],
enabled: false,
},
],
"ignoreDeps": [
"node",
]
}
8 changes: 3 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ on:
branches: [ main ]
push:
branches: [ main ]
schedule:
- cron: '0 0 * * *'
workflow_dispatch:

jobs:
Expand All @@ -19,7 +17,7 @@ jobs:

steps:
- name: Git Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
Expand All @@ -46,12 +44,12 @@ jobs:

strategy:
matrix:
php-version: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
php-version: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
dependency-version: [prefer-lowest, prefer-stable]

steps:
- name: Git Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
Expand Down
69 changes: 31 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
<div align="center">
<h1>PHPStan Friendly Formatter</h1>
A simple error formatter for <a href="https://phpstan.org/">PHPStan</a> that display code frame.
</div>
<p align="center">
<table>
<tr>
<th>Before</th>
<td><img src="./docs/example-before.png" alt="PHPStan Example">
</td>
</tr>
<tr>
<th>After</th>
<td><img src="./docs/example.png" alt="PHPStan Example">
</td>
</tr>
</table>
</p>
<p align="center">
<a href="https://packagist.org/packages/yamadashy/phpstan-friendly-formatter"><img src="https://shields.io/packagist/dt/yamadashy/phpstan-friendly-formatter" alt="Downloads"></a>
<a href="https://github.com/yamadashy/phpstan-friendly-formatter/actions"><img src="https://img.shields.io/github/actions/workflow/status/yamadashy/phpstan-friendly-formatter/tests.yml?branch=main&label=tests&logo=github" alt="Test Status"></a>
<a href="https://packagist.org/packages/yamadashy/phpstan-friendly-formatter"><img src="https://poser.pugx.org/yamadashy/phpstan-friendly-formatter/v/stable.svg" alt="Latest Version"></a>
<a href="https://github.com/yamadashy/phpstan-friendly-formatter/blob/master/LICENSE.md"><img src="https://poser.pugx.org/yamadashy/phpstan-friendly-formatter/license.svg" alt="License"></a>
</p>

---

# Motivation
The default phpstan formatter displays the file path, line number, and error, but this does not allow us to instantly determine what is actually wrong.

This package aims to complement the default formatter by displaying the corresponding source code alongside the error information, making it easier to locate and address issues more

# Getting Started
# 🤝 PHPStan Friendly Formatter

[![Downloads](https://shields.io/packagist/dt/yamadashy/phpstan-friendly-formatter)](https://packagist.org/packages/yamadashy/phpstan-friendly-formatter)
[![Test Status](https://img.shields.io/github/actions/workflow/status/yamadashy/phpstan-friendly-formatter/tests.yml?branch=main&label=tests&logo=github)](https://github.com/yamadashy/phpstan-friendly-formatter/actions)
[![Latest Version](https://poser.pugx.org/yamadashy/phpstan-friendly-formatter/v/stable.svg)](https://packagist.org/packages/yamadashy/phpstan-friendly-formatter)
[![License](https://poser.pugx.org/yamadashy/phpstan-friendly-formatter/license.svg)](https://github.com/yamadashy/phpstan-friendly-formatter/blob/master/LICENSE.md)

Enhance your [PHPStan](https://phpstan.org/) experience with a formatter that brings your code to life! 🚀

## 🌟 Features

- **Display Code Frame**: See the problematic code right where the error occurs
- **Error Identifier Summary**: Get a quick overview of error types and their frequencies
- **Beautiful Output**: Enjoy a visually appealing and easy-to-read error report

<img src="./docs/example.png" alt="PHPStan Friendly Formatter Example" width="100%">

## 🎯 Motivation

Ever felt lost in a sea of file paths and line numbers? We've been there! That's why we created this formatter to:

- Instantly pinpoint what's wrong in your code
- Reduce mental overhead when interpreting error messages
- Accelerate your debugging process

## 🚀 Getting Started

1. You may use [Composer](https://getcomposer.org/) to install this package as a development dependency.
```shell
Expand All @@ -48,7 +41,7 @@ includes:
./vendor/bin/phpstan analyze --error-format friendly
```

## Optional
### Optional: Simplify Your Workflow
If you want to make it simpler, setting `scripts` in `composer.json` as follows:

```json
Expand All @@ -65,7 +58,7 @@ composer analyze
```


# Config
## ⚙️ Configuration Options
You can customize in your `phpstan.neon`:
```neon
parameters:
Expand All @@ -82,11 +75,11 @@ parameters:
- `editorUrl` ... URL with placeholders like [table formatter config](URL for editor like table formatter)


# Example
## 🖼️ Example
When you actually introduce it in GitHub Actions, it will be displayed as follows.

![](./docs/github-actions.png)
![PHPStan Friendly Formatter output in GitHub Actions](./docs/github-actions.png)
https://github.com/yamadashy/laravel-blade-minify-directive/actions/runs/4714024802/jobs/8360104870

# License
## 📜 License
Distributed under the [MIT license](LICENSE.md).
Binary file removed docs/example-before.png
Binary file not shown.
Binary file modified docs/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ parametersSchema:

parameters:
friendly:
lineBefore: 3
lineAfter: 3
lineBefore: 2
lineAfter: 2
editorUrl: null

services:
Expand Down
7 changes: 3 additions & 4 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ includes:
- vendor/phpstan/phpstan-phpunit/rules.neon

parameters:
level: 8
level: 9
paths:
- src
- tests
Expand Down Expand Up @@ -38,6 +38,5 @@ parameters:
path: src/CodeHighlighter.php

friendly:
lineBefore: 3
lineAfter: 3
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
lineBefore: 2
lineAfter: 2
22 changes: 18 additions & 4 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
>
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects"
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnPhpunitDeprecations="true"
failOnPhpunitDeprecation="true"
failOnRisky="true"
failOnWarning="true">
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">./tests</directory>
<directory>tests</directory>
</testsuite>
</testsuites>

<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>
100 changes: 70 additions & 30 deletions src/FriendlyErrorFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in
$this->writeFileSpecificErrors($analysisResult, $output);
$this->writeNotFileSpecificErrors($analysisResult, $output);
$this->writeWarnings($analysisResult, $output);
$this->writeGroupedErrorsSummary($analysisResult, $output);
$this->writeFinalMessage($analysisResult, $output);

return 1;
Expand All @@ -52,37 +53,56 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in
private function writeFileSpecificErrors(AnalysisResult $analysisResult, Output $output): void
{
$codeHighlighter = new CodeHighlighter();
$errorsByFile = [];

foreach ($analysisResult->getFileSpecificErrors() as $error) {
$message = $error->getMessage();
$tip = $this->getFormattedTip($error);
$filePath = $error->getTraitFilePath() ?? $error->getFilePath();
$relativeFilePath = $this->relativePathHelper->getRelativePath($filePath);
$line = $error->getLine();
$fileContent = null;

if (file_exists($filePath)) {
$fileContent = (string) file_get_contents($filePath);
}
$errorsByFile[$relativeFilePath][] = $error;
}

if (null === $fileContent) {
$codeSnippet = ' <fg=#888><no such file></>';
} elseif (null === $line) {
$codeSnippet = ' <fg=#888><unknown file line></>';
} else {
$codeSnippet = $codeHighlighter->highlight($fileContent, $line, $this->lineBefore, $this->lineAfter);
}
foreach ($errorsByFile as $relativeFilePath => $errors) {
$output->writeLineFormatted($relativeFilePath);
$output->writeLineFormatted(str_repeat('-', mb_strlen($relativeFilePath)));
$output->writeLineFormatted('');

$output->writeLineFormatted(" <fg=red;options=bold>✘</> <fg=default;options=bold>{$message}</>");
if (null !== $tip) {
$output->writeLineFormatted(" <fg=default>Tip. {$tip}</>");
foreach ($errors as $error) {
$message = $error->getMessage();
$tip = $this->getFormattedTip($error);
$errorIdentifier = $error->getIdentifier();
$filePath = $error->getTraitFilePath() ?? $error->getFilePath();
$line = $error->getLine();
$fileContent = null;

if (file_exists($filePath)) {
$fileContent = (string) file_get_contents($filePath);
}

if (null === $fileContent) {
$codeSnippet = ' <fg=#888><no such file></>';
} elseif (null === $line) {
$codeSnippet = ' <fg=#888><unknown file line></>';
} else {
$codeSnippet = $codeHighlighter->highlight($fileContent, $line, $this->lineBefore, $this->lineAfter);
}

$output->writeLineFormatted(" <fg=red;options=bold>✘</> <fg=default;options=bold>{$message}</>");

if (null !== $tip) {
$output->writeLineFormatted(" <fg=default>💡 {$tip}</>");
}

if (null !== $errorIdentifier) {
$output->writeLineFormatted(" <fg=default>🪪 {$errorIdentifier}</>");
}

if (\is_string($this->editorUrl)) {
$output->writeLineFormatted(' ✏️ '.str_replace(['%file%', '%line%'], [$error->getTraitFilePath() ?? $error->getFilePath(), (string) $error->getLine()], $this->editorUrl));
}

$output->writeLineFormatted($codeSnippet);
$output->writeLineFormatted('');
}
$output->writeLineFormatted(" at <fg=cyan>{$relativeFilePath}</>:<fg=cyan>{$line}</>");
if (\is_string($this->editorUrl)) {
$output->writeLineFormatted(' ✏️ '.str_replace(['%file%', '%line%'], [$error->getTraitFilePath() ?? $error->getFilePath(), (string) $error->getLine()], $this->editorUrl));
}
$output->writeLineFormatted($codeSnippet);
$output->writeLineFormatted('');
}
}

Expand All @@ -102,13 +122,36 @@ private function writeWarnings(AnalysisResult $analysisResult, Output $output):
}
}

private function writeGroupedErrorsSummary(AnalysisResult $analysisResult, Output $output): void
{
/** @var array<string, int> $errorCounter */
$errorCounter = [];

foreach ($analysisResult->getFileSpecificErrors() as $error) {
$identifier = $error->getIdentifier() ?? 'unknown';
if (!\array_key_exists($identifier, $errorCounter)) {
$errorCounter[$identifier] = 0;
}
++$errorCounter[$identifier];
}

arsort($errorCounter);

$output->writeLineFormatted('📊 Error Identifier Summary:');
$output->writeLineFormatted('────────────────────────────');

foreach ($errorCounter as $identifier => $count) {
$output->writeLineFormatted(\sprintf(' %d %s', $count, $identifier));
}
}

private function writeFinalMessage(AnalysisResult $analysisResult, Output $output): void
{
$warningsCount = \count($analysisResult->getWarnings());
$finalMessage = sprintf(1 === $analysisResult->getTotalErrorsCount() ? 'Found %d error' : 'Found %d errors', $analysisResult->getTotalErrorsCount());
$finalMessage = \sprintf(1 === $analysisResult->getTotalErrorsCount() ? 'Found %d error' : 'Found %d errors', $analysisResult->getTotalErrorsCount());

if ($warningsCount > 0) {
$finalMessage .= sprintf(1 === $warningsCount ? ' and %d warning' : ' and %d warnings', $warningsCount);
$finalMessage .= \sprintf(1 === $warningsCount ? ' and %d warning' : ' and %d warnings', $warningsCount);
}

if ($analysisResult->getTotalErrorsCount() > 0) {
Expand All @@ -118,10 +161,7 @@ private function writeFinalMessage(AnalysisResult $analysisResult, Output $outpu
}
}

/**
* @return null|string
*/
private function getFormattedTip(Error $error)
private function getFormattedTip(Error $error): ?string
{
$tip = $error->getTip();

Expand Down
2 changes: 1 addition & 1 deletion tests/CodeHighlighterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class CodeHighlighterTest extends TestCase
/**
* @return \Generator<string, (int|string)[], void, void>
*/
public function provideHighlightCases(): iterable
public static function provideHighlightCases(): iterable
{
yield 'show 3 lines before and after' => [
__DIR__.'/data/AnalysisTargetFoo.php',
Expand Down
Loading

0 comments on commit 1f0809b

Please sign in to comment.