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
Conversation
…ary::changed property
TextReporter
pointing to official documentation
src/Fixer/FixerInterface.php
Outdated
* Returns the absolute URI [RFC3986](http://www.rfc-editor.org/info/rfc3986) | ||
* of the primary documentation for the fixer. | ||
*/ | ||
public function getHelpUri(): string; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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.
There was a problem hiding this comment.
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 !
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 !
src/Runner/Runner.php
Outdated
@@ -176,6 +177,10 @@ private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResu | |||
$tokens->clearEmptyTokens(); | |||
$tokens->clearChanged(); | |||
$appliedFixers[] = $fixer->getName(); | |||
|
|||
if (!isset($extraInfoFixers['helpUri'][$fixer->getName()])) { | |||
$extraInfoFixers['helpUri'][$fixer->getName()] = $fixer->getHelpUri(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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 !
@@ -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{}}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not:
* @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
* @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 🙂.
There was a problem hiding this comment.
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)
Now, after applying locally changes to initial PR proposal, and checks that all match my goals, I'll try to give you my best explains ;-) From @Wirone comment about Now, there are :
<?php
namespace PhpCsFixer\Fixer;
interface FixerReportInterface
{
public function getHelpUri(): string;
public function getExtraInfoFixers(): array;
}
<?php
abstract class AbstractFixer implements FixerInterface, FixerReportInterface
{
use FixerReportTrait;
}
<?php
namespace PhpCsFixer\Fixer;
use PhpCsFixer\Utils;
trait FixerReportTrait
{
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 getExtraInfoFixers(): array
{
return [
'helpUri' => $this->getHelpUri(),
'descriptions' => [
'short' => $this->getDefinition()->getSummary(),
'full' => $this->getDefinition()->getDescription(),
'risky' => $this->getDefinition()->getRiskyDescription() ?? $this->isRisky(),
],
];
}
} NB:
E.g: chunk of result generated by my "rules": [
{
"id": "phpdoc_no_alias_tag",
"name": "PhpdocNoAliasTag",
"shortDescription": {
"text": "No alias PHPDoc tags should be used."
},
"fullDescription": {
"text": "There is no description of Fixer and benefit of using it"
},
"defaultConfiguration": {
"enabled": true,
"level": "warning",
"rank": 1.0
},
"helpUri": "https://cs.symfony.com/doc/rules/phpdoc/phpdoc_no_alias_tag.html",
"help": {
"text": "https://cs.symfony.com/"
},
"properties": {
"frequency": 1,
"risky": false
}
}
] @Wirone If you're agree with these changes, I can push it to update the PR ! Let me know, if something is not enough clear ? |
I've found a better fix/improvement, following https://phpstan.org/writing-php-code/phpdoc-types#array-shapes
That will cover all needs. Only "helpUri" is a new info/feature, "definition" and "risky" source already exists. <?php
namespace PhpCsFixer\Fixer;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
interface FixerReportInterface
{
/**
* @return array{"helpUri"?: string, "definition"?: FixerDefinitionInterface, "risky"?: bool}
*/
public function getExtraInfoFixers(): array;
} And default implementation could be directly included into <?php
namespace PhpCsFixer;
abstract class AbstractFixer implements FixerInterface, FixerReportInterface
{
protected 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 getExtraInfoFixers(): array
{
return [
'helpUri' => $this->getHelpUri(),
'definition' => $this->getDefinition(),
'risky' => $this->isRisky(),
];
}
} And <?php
namespace PhpCsFixer\Runner;
final class Runner
{
private function fixFile(\SplFileInfo $file, LintingResultInterface $lintingResult): ?array
{
if ($tokens->isChanged()) {
$tokens->clearEmptyTokens();
$tokens->clearChanged();
$appliedFixers[] = $fixer->getName();
if ($fixer instanceof FixerReportInterface) {
$extraInfoFixers[$fixer->getName()] = $fixer->getExtraInfoFixers();
}
}
if ($oldHash !== $newHash) {
$fixInfo = [
'appliedFixers' => $appliedFixers,
'diff' => $this->differ->diff($old, $new, $file),
'extraInfoFixers' => $extraInfoFixers,
];
}
}
} |
@Wirone, @kubawerlos I've pushed on my repo (branch Following the same principle in action since https://github.com/llaville/sarif-php-sdk/releases/tag/1.2.0 for PHPCS, PHPLint, and PHPStan SARIF converters |
'definition' => $this->getDefinition(), | ||
'risky' => $this->isRisky(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh? no proxy methods please
we already have isRisky()
and getDefinition()
, no need for method that is duplicating this knowledge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't want to have external SARIF output format to your project I understand. Otherwise, consider that, as helpUri
the extra info that missed from ReportSummary
class, to generate full (validated) SARIF results which identify complety all rules/fixers applied !
I know that is not in context of this PR, but as we try to add an additional info (helpUri
) here I took advange of the situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we should focus on minimal changes required for achieving clickable URLs in the output - nothing more, nothing less. That would make this PR much easier to process and finish. Then, it could be extender or/and refactored to achieve SARIF reporting - for now I don't even have the context what it is, what it needs and I don't want to dig into that at this point. Having said that, I agree with @keradus' comment, and I would prefer first option from this comment. Let's don't model stuff for things that will or won't land later.
That's why I don't like FixerReportInterface
because we don't need to focus here on reporting (clickable URL can be also added to describe
CLI command), I would prefer something more atomic, focused on docs URI. ProvidesDocumentationUriInterface
or something like that, that would allow adding only this particular feature to fixers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: If you wan't to know what is SARIF, consider to take time to read this page https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning.
It's a good intro !
@@ -41,6 +43,7 @@ public function generate(ReportSummary $reportSummary): string | |||
$output .= $this->getAppliedFixers( | |||
$reportSummary->isDecoratedOutput(), | |||
$fixResult['appliedFixers'], | |||
$fixResult['extraInfoFixers'] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea, but we overkill the implementation imho
As all project leaders, you're the last one to choose.
Please let me know your answer, and I will heard it. |
FYI: it works fine with custom fixers like https://github.com/kubawerlos/php-cs-fixer-custom-fixers Fixers that don't implement new Interface (whatever is name should be) |
Closing this PR. |
There's no need to close the PR though, everyone works at their own pace and I just did not have the possibility to look at this more during the weekend. I already provided info on how I see it, so the scope of this PR could be narrowed to a simple 1-method interface, with implementation focused only on the clickable rule name. The name of the interface could be then discussed. I don't want to point every bit of the code (what and where), this way I would rather prefer to do the implementation myself |
Related to discussion #7996