Skip to content

Commit

Permalink
add converter support to Rector
Browse files Browse the repository at this point in the history
  • Loading branch information
llaville committed Jul 27, 2024
1 parent 1a28078 commit b503271
Show file tree
Hide file tree
Showing 20 changed files with 691 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ This library may be considered as a producer on `sarif` report format, for these
[phpmd]: https://github.com/phpmd/phpmd
[phpstan]: https://github.com/phpstan/phpstan
[psalm]: https://github.com/vimeo/psalm
[rector]: https://github.com/rectorphp/rector
[twigcs-fixer]: https://github.com/VincentLanglet/Twig-CS-Fixer

| Project | Version | SARIF | Checkstyle | JUnit | CodeClimate<br/>GitLab | GitHub | Output Format Support |
Expand All @@ -61,6 +62,7 @@ This library may be considered as a producer on `sarif` report format, for these
| [PHP Mess Detector][phpmd] | 2.15.x |||||| xml,text,html,json,github,gitlab,sarif,checkstyle |
| [PHPStan][phpstan] | 1.11.x | (✅) ||||| table,raw,checkstyle,json,junit,github,gitlab,teamcity,(sarif) |
| [Psalm][psalm] | 5.x |||||| console,checkstyle,json,junit,github,codeclimate,pylint,sonarqube,sarif |
| [Rector][rector] | 1.x |||||| console,json |
| [Twig-CS-Fixer][twigcs-fixer] | 3.0.x |||||| null,text,checkstyle,junit,github |

Legend :
Expand Down
227 changes: 227 additions & 0 deletions docs/assets/images/converter-rector.graphviz.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/phpstorm-rector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/converter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
| PHP Mess Detector [official website][phpmd] | [phpmd](phpmd.md) | required ||
| PHPStan [official website][phpstan] | [phpstan](phpstan.md) | optional ||
| Psalm [official website][psalm] | [psalm](psalm.md) | required ||
| Rector [official website][rector] | [rector](rector.md) | optional ||
| Twig-CS-Fixer [official website][twigcs-fixer] | [twigcs-fixer](twigcs-fixer.md) | optional ||

[ecs]: https://github.com/easy-coding-standard/easy-coding-standard
Expand All @@ -26,4 +27,5 @@
[phpmd]: https://github.com/phpmd/phpmd
[phpstan]: https://github.com/phpstan/phpstan
[psalm]: https://github.com/vimeo/psalm
[rector]: https://github.com/rectorphp/rector
[twigcs-fixer]: https://github.com/VincentLanglet/Twig-CS-Fixer
88 changes: 88 additions & 0 deletions docs/converter/rector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!-- markdownlint-disable MD013 -->
# Rector Converter

[![rectorphp/rector - GitHub](https://gh-card.dev/repos/rectorphp/rector.svg?fullname=)](https://github.com/rectorphp/rector)

> [!NOTE]
>
> Available since version 1.0.0
## Table Of Contents

1. [Requirements](#requirements)
2. [Installation](#installation)
3. [Usage](#usage)
4. [Learn more](#learn-more)
5. [IDE Integration](#ide-integration)
6. [Web SARIF viewer](#web-sarif-viewer)

![rector converter](../assets/images/converter-rector.graphviz.svg)

## Requirements

* [Rector][rector] requires PHP version 7.2.0 or greater, with `phpstan` 1.11 or greater
* This SARIF converter requires at least Rector version 1.0

## Installation

```shell
composer require --dev rector/rector bartlett/sarif-php-converters
```

## Usage

**Step 1:** Update your `rector.php` configuration file

Register at least the `RectorFormatter` service to be able to specify `--output-format sarif` with rector command.

```php
<?php
use Bartlett\Sarif\Converter\Reporter\RectorFormatter;

use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
])
->withPreparedSets(true)
->withRealPathReporting()
->withBootstrapFiles([__DIR__ . '/../../vendor/autoload.php']) // loader for Sarif PHP Converters classes
->registerService(RectorFormatter::class, null, OutputFormatterInterface::class)
;
```

**Step 2:** Then print the SARIF report

```shell
vendor/bin/rector process --dry-run --output-format sarif --config /path/to/rector.php > .sarif.json
```

> [!WARNING]
>
> Be sure to specify `withRealPathReporting`, otherwise the Console Tool `convert` command
> will raise some warnings about file names.
> Requires at least [feature](https://github.com/rectorphp/rector/issues/8757) is implemented in a future Rector release.
## Learn more

* See demo [`examples/rector/`][example-folder] directory into this repository.

## IDE Integration

The SARIF report file `[*].sarif.json` is automagically recognized and interpreted by PhpStorm (2024).

![PHPStorm integration](../assets/images/phpstorm-rector.png)

## Web SARIF viewer

With the [React based component][sarif-web-component], you are able to explore a sarif report file previously generated.

For example:

![sarif-web-rector](../assets/images/sarif-web-rector.png)

[example-folder]: https://github.com/llaville/sarif-php-converters/blob/1.0/examples/rector/
[rector]: https://github.com/rectorphp/rector
[sarif-web-component]: https://github.com/Microsoft/sarif-web-component
1 change: 1 addition & 0 deletions examples/rector/.sarif.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"$schema":"https://json.schemastore.org/sarif-2.1.0.json","version":"2.1.0","runs":[{"tool":{"driver":{"name":"Rector","shortDescription":{"text":"PHP Refactoring code"},"fullDescription":{"text":"Instant Upgrade and Automated Refactoring of any PHP code"},"semanticVersion":"dev-main","informationUri":"https://getrector.com","rules":[{"id":"RECTOR/DeadCode/ClassMethod","name":"RemoveUnusedPrivateMethodParameterRector","shortDescription":{"text":"Remove unused parameter, if not required by interface or parent class"},"helpUri":"https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#removeunusedprivatemethodparameterrector","properties":{"configurable":false}},{"id":"RECTOR/DeadCode/Assign","name":"RemoveUnusedVariableAssignRector","shortDescription":{"text":"Remove unused assigns to variables"},"helpUri":"https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#removeunusedvariableassignrector","properties":{"configurable":false}},{"id":"RECTOR/CodeQuality/FunctionLike","name":"SimplifyUselessVariableRector","shortDescription":{"text":"Removes useless variable assigns"},"helpUri":"https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#simplifyuselessvariablerector","properties":{"configurable":true}},{"id":"RECTOR/SystemError"}]},"extensions":[{"name":"bartlett/sarif-php-converters","shortDescription":{"text":"Rector SARIF Converter"},"version":"1.0.9999999.9999999-dev"}]},"invocations":[{"executionSuccessful":true,"commandLine":"/shared/backups/forks/rector-src/bin/rector","arguments":["process","--dry-run","--output-format","sarif","--config","examples/rector/rector.php"],"workingDirectory":{"uri":"file:///shared/backups/bartlett/sarif-php-converters/"},"properties":{"changed_files":6}}],"originalUriBaseIds":{"WORKINGDIR":{"uri":"file:///shared/backups/bartlett/sarif-php-converters/"}},"results":[{"message":{"text":"Applied rule RemoveUselessReturnTagRector"},"ruleId":"RECTOR/DeadCode/ClassMethod","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Contract/ConverterInterface.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/ClassMethod":"3326e444522cdd8facde887fa1960a74bd5dc7503a5fc3a1317116774ef6af3c"}},{"message":{"text":"Applied rule RemoveUnusedVariableAssignRector"},"ruleId":"RECTOR/DeadCode/Assign","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/EcsNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/Assign":"6800590f849f76621b065eacea6cbb0dc87e2a1556d051d8af92a12fd862ae97"}},{"message":{"text":"Applied rule RemoveUnusedPrivateMethodParameterRector"},"ruleId":"RECTOR/DeadCode/ClassMethod","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/EcsNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/ClassMethod":"6800590f849f76621b065eacea6cbb0dc87e2a1556d051d8af92a12fd862ae97"}},{"message":{"text":"Applied rule RemoveUnusedVariableAssignRector"},"ruleId":"RECTOR/DeadCode/Assign","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/PhanNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/Assign":"a99dcdffc7d119bace1aeeb609c6607c633663cdbcbaf9f70bd01d0aef0db104"}},{"message":{"text":"Applied rule RemoveUnusedPrivateMethodParameterRector"},"ruleId":"RECTOR/DeadCode/ClassMethod","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/PhanNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/ClassMethod":"a99dcdffc7d119bace1aeeb609c6607c633663cdbcbaf9f70bd01d0aef0db104"}},{"message":{"text":"Applied rule RemoveUnusedVariableAssignRector"},"ruleId":"RECTOR/DeadCode/Assign","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/PhpCsFixerNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/Assign":"09ce8c52a5fe769d80c7fadb702589215b3041ac6abba478c243e5d85ec2ecbc"}},{"message":{"text":"Applied rule RemoveUnusedPrivateMethodParameterRector"},"ruleId":"RECTOR/DeadCode/ClassMethod","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/PhpCsFixerNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/ClassMethod":"09ce8c52a5fe769d80c7fadb702589215b3041ac6abba478c243e5d85ec2ecbc"}},{"message":{"text":"Applied rule SimplifyUselessVariableRector"},"ruleId":"RECTOR/CodeQuality/FunctionLike","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/RectorNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/CodeQuality/FunctionLike":"d734c199b053768bfe3c79459f5b0f3484b114c2dbf5c3cde5ebcef75dcdfbf1"}},{"message":{"text":"Applied rule RemoveUnusedPrivateMethodParameterRector"},"ruleId":"RECTOR/DeadCode/ClassMethod","locations":[{"physicalLocation":{"artifactLocation":{"uri":"src/Converter/Normalizer/TwigCsFixerNormalizer.php","uriBaseId":"WORKINGDIR"}}}],"partialFingerprints":{"RECTOR/DeadCode/ClassMethod":"89b6fb20d5952af114ce920d1e24262af8d7da2aa7077345b9f40b88c62d5281"}},{"message":{"text":"Syntax error, unexpected '}'"},"ruleId":"RECTOR/SystemError","locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/fixtures/php_bad_2.php","uriBaseId":"WORKINGDIR"},"region":{"startLine":15,"snippet":{"rendered":{"text":" > 15| } elseif ($condition) {}"}}},"contextRegion":{"startLine":13,"endLine":17,"snippet":{"rendered":{"text":" > 13| $condition = rand(0, 5);\n 14| iff ($condition) {\n 15| } elseif ($condition) {}\n 16| "}}}}}],"partialFingerprints":{"RECTOR/SystemError":"2e508d59ec5ba941128ccdf751869d555c2c618018b0979124ee4530b68fbc28"}},{"message":{"text":"Syntax error, unexpected T_STRING, Syntax error, unexpected '=', Invalid numeric literal, Syntax error, unexpected T_LNUMBER, Invalid numeric literal, Syntax error, unexpected T_STRING"},"ruleId":"RECTOR/SystemError","locations":[{"physicalLocation":{"artifactLocation":{"uri":"examples/fixtures/php_bad_1.php","uriBaseId":"WORKINGDIR"},"region":{"startLine":3,"snippet":{"rendered":{"text":" > 3| 2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29"}}},"contextRegion":{"startLine":1,"endLine":5,"snippet":{"rendered":{"text":" > 1| <?php\n 2| \n 3| 2pe98y r-n0u823n=r 092u3- r08u2q098ry 09nq2yr09n2yr9 y2n-93yr 298yr3 29\n 4| "}}}}}],"partialFingerprints":{"RECTOR/SystemError":"b72968e694d9f576c2adc3cdc3f479a0834ab688b7dc8a6fe2657d59e034ae1f"}}],"automationDetails":{"id":"Daily run 2024-07-27T08:05:50+00:00"}}]}
4 changes: 4 additions & 0 deletions examples/rector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- markdownlint-disable MD013 -->
# Rector Converter Demo

Refer to specialized [Converter Guide](../../docs/converter/rector.md) for explains.
26 changes: 26 additions & 0 deletions examples/rector/rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/**
* This file is part of the Sarif-PHP-Converters package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @author Laurent Laville
* @since Release 1.0.0
*/

use Bartlett\Sarif\Converter\Reporter\RectorFormatter;

use Rector\ChangesReporting\Contract\Output\OutputFormatterInterface;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
->withPaths([
__DIR__ . '/../../examples',
__DIR__ . '/../../src',
])
->withPreparedSets(true)
->withRealPathReporting()
->withBootstrapFiles([__DIR__ . '/../../vendor/autoload.php'])
->registerService(RectorFormatter::class, null, OutputFormatterInterface::class)
;
21 changes: 21 additions & 0 deletions resources/converter-rector/datasource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types=1);
/**
* This file is part of the Sarif-PHP-Converters package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @author Laurent Laville
* @since Release 1.0.0
*/

return function (): Generator {
$classes = [
\Bartlett\Sarif\Converter\RectorConverter::class,
\Bartlett\Sarif\Converter\Source\RectorSource::class,
\Bartlett\Sarif\Converter\Normalizer\RectorNormalizer::class,
];
foreach ($classes as $class) {
yield $class;
}
};
20 changes: 20 additions & 0 deletions resources/converter-rector/options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);
/**
* This file is part of the Sarif-PHP-Converters package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @author Laurent Laville
* @since Release 1.0.0
*/

return [
// @link https://graphviz.gitlab.io/docs/attrs/rankdir/
'graph.rankdir' => 'TB',
// @link https://plantuml.com/en/color
'cluster.Bartlett\Sarif\Converter.graph.bgcolor' => 'BurlyWood',
'cluster.Bartlett\Sarif\Contract.graph.bgcolor' => 'LightSkyBlue',
'cluster.Bartlett\Sarif\Converter\Source.graph.bgcolor' => 'Bisque',
'cluster.Bartlett\Sarif\Converter\Normalizer.graph.bgcolor' => 'Bisque',
];
1 change: 1 addition & 0 deletions resources/gh-pages-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ php $SCRIPT_DIR/build.php converter-phplint $ASSETS_IMAGE_DIR
php $SCRIPT_DIR/build.php converter-phpmd $ASSETS_IMAGE_DIR
php $SCRIPT_DIR/build.php converter-phpstan $ASSETS_IMAGE_DIR
php $SCRIPT_DIR/build.php converter-psalm $ASSETS_IMAGE_DIR
php $SCRIPT_DIR/build.php converter-rector $ASSETS_IMAGE_DIR
php $SCRIPT_DIR/build.php converter-twigcs-fixer $ASSETS_IMAGE_DIR
1 change: 1 addition & 0 deletions src/Contract/ConverterFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface ConverterFactoryInterface
public const BUILTIN_CONVERTER_PHPMD = 'phpmd';
public const BUILTIN_CONVERTER_PHPSTAN = 'phpstan';
public const BUILTIN_CONVERTER_PSALM = 'psalm';
public const BUILTIN_CONVERTER_RECTOR = 'rector';
public const BUILTIN_CONVERTER_TWIG_CS_FIXER = 'twigcs-fixer';

/**
Expand Down
1 change: 1 addition & 0 deletions src/Contract/SourceFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface SourceFactoryInterface
public const BUILTIN_SOURCE_PHPCSFIXER = 'phpcs-fixer';
public const BUILTIN_SOURCE_PHPLINT = 'phplint';
public const BUILTIN_SOURCE_PHPMD = 'phpmd';
public const BUILTIN_SOURCE_RECTOR = 'rector';
public const BUILTIN_SOURCE_PHPSTAN = 'phpstan';
public const BUILTIN_SOURCE_PSALM = 'psalm';
public const BUILTIN_SOURCE_TWIG_CS_FIXER = 'twigcs-fixer';
Expand Down
146 changes: 146 additions & 0 deletions src/Converter/Normalizer/RectorNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php declare(strict_types=1);
/**
* This file is part of the Sarif-PHP-Converters package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Bartlett\Sarif\Converter\Normalizer;

use Bartlett\Sarif\Contract\NormalizerInterface;

use Rector\Contract\Rector\ConfigurableRectorInterface;
use Rector\Contract\Rector\RectorInterface;
use Rector\ValueObject\ProcessResult;

use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;

use ArrayObject;
use ReflectionClass;
use ReflectionException;
use function array_unique;
use function count;
use function explode;
use function in_array;
use function is_a;
use function sprintf;
use function strtolower;

/**
* @author Laurent Laville
* @since Release 1.0.0
*/
final class RectorNormalizer implements NormalizerInterface
{
public const URI_PATTERN = 'https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md#%s';

public function getSupportedFormats(): array
{
return [
NormalizerInterface::FORMAT_INTERNAL,
];
}

public function normalize($data, string $format, array $context): ?ArrayObject
{
if (!in_array($format, $this->getSupportedFormats())) {
return null;
}

if (!$data instanceof ProcessResult) {
return null;
}

// internal format (legacy)
return new ArrayObject($this->fromInternal($data, $context));
}

/**
* @return array{files: mixed, errors: mixed, rules: mixed}
*/
private function fromInternal(ProcessResult $processResult, array $context): array
{
$files = [];
$errors = [];
$rules = [];

$fileDiffs = $processResult->getFileDiffs();

foreach ($fileDiffs as $fileDiff) {
$filePath = $fileDiff->getAbsoluteFilePath();
$files[] = $filePath;

foreach($fileDiff->getRectorClasses() as $rectorClass) {
// e.g: Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodParameterRector
$nameParts = explode('\\', $rectorClass);

$standard = $nameParts[1]; // e.g: DeadCode
$group = $nameParts[3]; // e.g: ClassMethod
$rectorShortClass = $nameParts[count($nameParts) - 1]; // e.g: RemoveUnusedPrivateMethodParameterRector
$helpUri = sprintf(self::URI_PATTERN, strtolower($rectorShortClass));

$ruleId = sprintf('%s/%s/%s', $context['rulePrefix'], $standard, $group);

$rules[$ruleId] = [
'name' => $rectorShortClass,
'helpUri' => $helpUri,
];

$ruleDefinition = $this->resolveFromClassName($rectorClass);
if (null !== $ruleDefinition) {
$rules[$ruleId]['shortDescription'] = $ruleDefinition->getDescription();
}
$isRuleConfigurable = is_a($rectorClass, ConfigurableRectorInterface::class, true);
$rules[$ruleId]['properties']['configurable'] = $isRuleConfigurable;

$attributes = [
'ReportingDescriptor.id' => $ruleId,
'Result.message' => sprintf('Applied rule %s', $rectorShortClass),
];
$errors[$filePath][] = $attributes;
};
}

$systemErrors = $processResult->getSystemErrors();
foreach ($systemErrors as $error) {
$filePath = $error->getAbsoluteFilePath();
$files[] = $filePath;

$nameParts = explode('\\', \get_class($error));
$ruleId = sprintf('%s/%s', $context['rulePrefix'], end($nameParts));

$rules[$ruleId] = [
'name' => $error->getRectorClass(),
];

$attributes = [
'ReportingDescriptor.id' => $ruleId,
'Result.message' => $error->getMessage(),
'Region.startLine' => $error->getLine() ?? null,
];
$errors[$filePath][] = $attributes;
}

return [
'files' => array_unique($files),
'errors' => $errors,
'rules' => $rules,
];
}

private function resolveFromClassName(string $className): ?RuleDefinition
{
try {
$reflectionClass = new ReflectionClass($className);
$documentedRule = $reflectionClass->newInstanceWithoutConstructor();
} catch (ReflectionException $e) {
return null;
}

if ($documentedRule instanceof RectorInterface) {
return $documentedRule->getRuleDefinition();
}

return null;
}
}
Loading

0 comments on commit b503271

Please sign in to comment.