Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Clickable rule names in TextReporter pointing to official documentation #8003

Closed
13 changes: 13 additions & 0 deletions src/AbstractFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ public function isRisky(): bool
return false;
}

public function getHelpUri(): string
{
$nameParts = explode('\\', static::class);
$group = $nameParts[\count($nameParts) - 2];
$name = substr(end($nameParts), 0, -\strlen('Fixer'));

return sprintf(
'https://cs.symfony.com/doc/rules/%s/%s.html',
Utils::camelCaseToUnderscore($group),
Utils::camelCaseToUnderscore($name)
);
}

public function getName(): string
{
$nameParts = explode('\\', static::class);
Expand Down
10 changes: 5 additions & 5 deletions src/Console/Report/FixReport/ReportSummary.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
final class ReportSummary
{
/**
* @var array<string, array{appliedFixers: list<string>, diff: string}>
* @var array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}>
*/
private array $changed;

Expand All @@ -39,9 +39,9 @@ final class ReportSummary
private bool $isDecoratedOutput;

/**
* @param array<string, array{appliedFixers: list<string>, diff: string}> $changed
* @param int $time duration in milliseconds
* @param int $memory memory usage in bytes
* @param array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}> $changed
* @param int $time duration in milliseconds
* @param int $memory memory usage in bytes
*/
public function __construct(
array $changed,
Expand Down Expand Up @@ -72,7 +72,7 @@ public function isDryRun(): bool
}

/**
* @return array<string, array{appliedFixers: list<string>, diff: string}>
* @return array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not:

Suggested change
* @return array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}>
* @return array<string, array{appliedFixers: list<string>, diff: string, helpUris: null|array<string, string>}>

or

Suggested change
* @return array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}>
* @return array<string, array{appliedFixers: list<string>, diff: string, helpUris?: array<string, string>}>

The first one make the helpUri nullable, the second one makes it optional. In both cases we can detect if there's info with URIs or not. I also find extraInfoFixers superfluous, I would rather add one level field like helpUris (plural, as it's a collection).

In fact, I would like to do appliedFixers: list<array{name: string, helpUri: string}>, but Runner also is not internal so we can't change the format of the returned array. Adding new field should be OK, though.

@keradus please let me know what you think 🙂.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Wirone Stop wasting your time with this current PR. Even if code is almost ready, I should change the extraInfoFixers.

I'll give more explains when my new version will be ready. I hope to propose something in next hours (now, I'm back)

*/
public function getChanged(): array
{
Expand Down
29 changes: 25 additions & 4 deletions src/Console/Report/FixReport/TextReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
namespace PhpCsFixer\Console\Report\FixReport;

use PhpCsFixer\Differ\DiffConsoleFormatter;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
* @author Boris Gorbylev <[email protected]>
Expand All @@ -41,6 +42,7 @@ public function generate(ReportSummary $reportSummary): string
$output .= $this->getAppliedFixers(
$reportSummary->isDecoratedOutput(),
$fixResult['appliedFixers'],
$fixResult['extraInfoFixers']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really wonder if we need to change the fixResult object, or simply have one standalone method that is converting fixer-name to fixer-url

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will let you think again then ...

);
}

Expand All @@ -58,13 +60,32 @@ public function generate(ReportSummary $reportSummary): string
}

/**
* @param list<string> $appliedFixers
* @param list<string> $appliedFixers
* @param array{helpUri: array<string, string>|array{}}|array{} $extraInfoFixers
*/
private function getAppliedFixers(bool $isDecoratedOutput, array $appliedFixers): string
private function getAppliedFixers(bool $isDecoratedOutput, array $appliedFixers, array $extraInfoFixers = []): string
{
if (!isset($extraInfoFixers['helpUri'])) {
return sprintf(
$isDecoratedOutput ? ' (<comment>%s</comment>)' : ' (%s)',
implode(', ', $appliedFixers)
);
}

$fixers = [];

foreach ($appliedFixers as $appliedFixer) {
$url = $extraInfoFixers['helpUri'][$appliedFixer] ?? '';
if ($isDecoratedOutput && '' !== $url) {
$fixers[] = sprintf('<href=%s;fg=yellow>%s</>', OutputFormatter::escape($url), $appliedFixer);
} else {
$fixers[] = $appliedFixer;
}
}

return sprintf(
$isDecoratedOutput ? ' (<comment>%s</comment>)' : ' (%s)',
implode(', ', $appliedFixers)
' (<comment>%s</comment>)',
implode(', ', $fixers)
);
}

Expand Down
6 changes: 6 additions & 0 deletions src/Fixer/FixerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void;
*/
public function getDefinition(): FixerDefinitionInterface;

/**
* Returns the absolute URI [RFC3986](http://www.rfc-editor.org/info/rfc3986)
* of the primary documentation for the fixer.
*/
public function getHelpUri(): string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since FixerInterface is not internal (it's a contract for custom fixers too) this is a breaking change. Is it required to get this info straight from the fixers, or could it be done using an external class?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've recently released https://github.com/llaville/sarif-php-sdk/releases/tag/1.2.0 that will add SARIF support to most common PHP linters.

I've prepared a \Bartlett\Sarif\Converter\PhpCsFixerConverter for PHP-CS-Fixer too, that passed all validation by official Sarif Validator
But to be able to use it, the #7995 should be accepted ;-)

All, to said that SARIF used an helpUri property to enforce online documentation.

This is also my goal with PHP-CS-Fixer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that, but it does not change the fact it's a breaking change and requires major release, which we don't want now, as it would require massive amount of work to clean the codebase from deprecations ☺️. And it would require @keradus' review and approval, which often slows things down because of his limited availability. With non-invasive approach I think we can continue without him.

Having said that, we could make it part of a contract, but using composition - we can introduce a new interface, dedicated only for that, implement it internally to satisfy the reporter's needs, but at the same time let people implement it at their own pace. That would require checking if the fixer implements this interface while preparing the output and linking only these rules that provide a doc URL.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with you to do it, but I'm sorry I won't have enough free time today !

Copy link
Member

@Wirone Wirone May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem, everyone's busy. Let's continue as fast as possible, but without any rush ☺️.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Wirone I'll work now on your idea to split interfaces to have something like :

<?php
namespace PhpCsFixer\Fixer;

interface FixerReportInterface
{
    public function getHelpUri(): string;

    public function getExtraInfoFixers(): array;
}

The getExtraInfoFixers will return any additional info as helpUri (e.g), but much more.

This interface will be usefull, in state only for TextReporter, because other Reporter don't provide online doc by format spec !


/**
* Returns the name of the fixer.
*
Expand Down
10 changes: 8 additions & 2 deletions src/Runner/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
}

/**
* @return array<string, array{appliedFixers: list<string>, diff: string}>
* @return array<string, array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}>
*/
public function fix(): array
{
Expand Down Expand Up @@ -130,7 +130,7 @@
}

/**
* @return null|array{appliedFixers: list<string>, diff: string}
* @return null|array{appliedFixers: list<string>, diff: string, extraInfoFixers: array{helpUri: array<string, string>}|array{}}
*/
private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult): ?array
{
Expand Down Expand Up @@ -158,8 +158,9 @@
$newHash = $oldHash;

$appliedFixers = [];
$extraInfoFixers = [];

try {

Check warning on line 163 in src/Runner/Runner.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 mutation tests

Escaped Mutant for Mutator "CatchBlockRemoval": --- Original +++ New @@ @@ } } } - } catch (\ParseError $e) { - $this->dispatchEvent(FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT)); - $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); - return null; } catch (\Throwable $e) { $this->processException($name, $e); return null;

Check warning on line 163 in src/Runner/Runner.php

View workflow job for this annotation

GitHub Actions / PHP 8.3 mutation tests

Escaped Mutant for Mutator "CatchBlockRemoval": --- Original +++ New @@ @@ $this->dispatchEvent(FixerFileProcessedEvent::NAME, new FixerFileProcessedEvent(FixerFileProcessedEvent::STATUS_LINT)); $this->errorsManager->report(new Error(Error::TYPE_LINT, $name, $e)); return null; - } catch (\Throwable $e) { - $this->processException($name, $e); - return null; } $fixInfo = null; if ([] !== $appliedFixers) {
foreach ($this->fixers as $fixer) {
// for custom fixers we don't know is it safe to run `->fix()` without checking `->supports()` and `->isCandidate()`,
// thus we need to check it and conditionally skip fixing
Expand All @@ -176,6 +177,10 @@
$tokens->clearEmptyTokens();
$tokens->clearChanged();
$appliedFixers[] = $fixer->getName();

if (!isset($extraInfoFixers['helpUri'][$fixer->getName()])) {
$extraInfoFixers['helpUri'][$fixer->getName()] = $fixer->getHelpUri();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only place getHelpUrl is called, so we can have the logic from AbstractFixer::getHelpUri in here and do not break BC, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kubawerlos are you familiar with this comment? Because info about URIs would be used with another, dedicated reporter too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I did not verify it against that concept, so maybe you're right it would also work for it 🙂. Just wanted to make sure you have the context.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw it, and as in https://github.com/llaville/sarif-php-sdk/blob/1.2.0/src/Converter/PhpStanConverter.php the URLs are in there, not coming from PHPStan, so my understanding is that both PhpCsFixerConverter and Runner can have it (maybe shared by some new constant in PHP CS Fixer repo, but definitely not wich BC break change).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll give more explains later, especially in context of SARIF reports.
@kubawerlos The PhpCsFixerConverter is not yet available on my repo. Will come later when this PR will be finished !

}
}
}
} catch (\ParseError $e) {
Expand Down Expand Up @@ -208,6 +213,7 @@
$fixInfo = [
'appliedFixers' => $appliedFixers,
'diff' => $this->differ->diff($old, $new, $file),
'extraInfoFixers' => $extraInfoFixers,
];

try {
Expand Down
10 changes: 10 additions & 0 deletions tests/AbstractProxyFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ public function getDefinition(): FixerDefinitionInterface
throw new \BadMethodCallException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \BadMethodCallException('Not implemented.');
}

public function getName(): string
{
return uniqid('abstract_proxy_test_');
Expand Down Expand Up @@ -211,6 +216,11 @@ public function fix(\SplFileInfo $file, Tokens $tokens): void
throw new \BadMethodCallException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \BadMethodCallException('Not implemented.');
}

public function getName(): string
{
return uniqid('abstract_proxy_aware_test_');
Expand Down
10 changes: 10 additions & 0 deletions tests/Console/Command/DescribeCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,11 @@ public function getDefinition(): FixerDefinition
return new FixerDefinition('Fixes stuff.', []);
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return 'Foo/bar_baz';
Expand Down Expand Up @@ -578,6 +583,11 @@ public function getDefinition(): FixerDefinition
);
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return 'Foo/bar';
Expand Down
6 changes: 6 additions & 0 deletions tests/Console/Report/FixReport/AbstractReporterTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class Foo
{
}
}',
'extraInfoFixers' => [],
],
],
10,
Expand Down Expand Up @@ -122,6 +123,7 @@ class Foo
{
}
}',
'extraInfoFixers' => [],
],
],
10,
Expand All @@ -140,6 +142,7 @@ class Foo
'someFile.php' => [
'appliedFixers' => ['some_fixer_name_here_1', 'some_fixer_name_here_2'],
'diff' => '',
'extraInfoFixers' => [],
],
],
10,
Expand Down Expand Up @@ -168,6 +171,7 @@ class Foo
{
}
}',
'extraInfoFixers' => [],
],
],
10,
Expand All @@ -186,10 +190,12 @@ class Foo
'someFile.php' => [
'appliedFixers' => ['some_fixer_name_here_1', 'some_fixer_name_here_2'],
'diff' => 'this text is a diff ;)',
'extraInfoFixers' => [],
],
'anotherFile.php' => [
'appliedFixers' => ['another_fixer_name_here'],
'diff' => 'another diff here ;)',
'extraInfoFixers' => [],
],
],
10,
Expand Down
1 change: 1 addition & 0 deletions tests/Console/Report/FixReport/ReportSummaryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function testReportSummary(): void
'someFile.php' => [
'appliedFixers' => ['some_fixer_name_here'],
'diff' => 'this text is a diff ;)',
'extraInfoFixers' => [],
],
];
$filesCount = 10;
Expand Down
20 changes: 20 additions & 0 deletions tests/FixerFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ public function getDefinition(): FixerDefinitionInterface
throw new \LogicException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return 'foo';
Expand Down Expand Up @@ -458,6 +463,11 @@ public function getDefinition(): FixerDefinitionInterface
throw new \LogicException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return 'foo';
Expand Down Expand Up @@ -531,6 +541,11 @@ public function getDefinition(): FixerDefinitionInterface
throw new \LogicException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return 'foo';
Expand Down Expand Up @@ -586,6 +601,11 @@ public function getDefinition(): FixerDefinitionInterface
throw new \LogicException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function getName(): string
{
return $this->name;
Expand Down
3 changes: 3 additions & 0 deletions tests/Runner/RunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public function testThatFixSuccessfully(): void
$expectedChangedInfo = [
'appliedFixers' => ['visibility_required'],
'diff' => '',
'extraInfoFixers' => [
'helpUri' => ['visibility_required' => 'https://cs.symfony.com/doc/rules/class_notation/visibility_required.html'],
],
];

$path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'FixerTest'.\DIRECTORY_SEPARATOR.'fix';
Expand Down
5 changes: 5 additions & 0 deletions tests/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,11 @@ public function isRisky(): bool
throw new \LogicException('Not implemented.');
}

public function getHelpUri(): string
{
throw new \LogicException('Not implemented.');
}

public function fix(\SplFileInfo $file, Tokens $tokens): void
{
throw new \LogicException('Not implemented.');
Expand Down