phplint icon indicating copy to clipboard operation
phplint copied to clipboard

[v9.4] Add support to more output formats

Open llaville opened this issue 1 year ago • 0 comments

Summary

Simplify ability to add support to more formats

Context

Even if I consider SARIF as the futur of reporter solution, I must admit that PHP project leaders/maintainers are a little reluctant to set up this format, and prefer to implement checkstyle, junit, codeclimate ... to reference only few of them.

This is the reason why I've already provided a PHP binding solution https://github.com/llaville/sarif-php-sdk,, and I'm currently working on an improvement with upcoming version 2.0

My goal is to maintain all classes for PHP Linters and Static Code Analyser on a new package bartlett/sarif-php-converters and removed converters from base package package bartlett/sarif-php-sdk

See Reference below

Description

Even, If I've already implemented a solution into PHPLint, I must admit that it's a bit hard, and current OutputFormat extension did not allow to add support to more format easily. This is the MAIN goal of this feature request !

Recently, I've look on PHP Insights source code (especially the FormatResolver component), and I like ability to load custom format (not predefined). BTW, it suffer from a problem that PHPLint has not : the bootstrapping option.

This is the main reason of new upcoming version 9.4.0 (/cc @overtrue)

Secondary goal is to simplify OutputFormat extension and respect the O (SRP) of SOLID principle.

And last but not least, clean-up SARIF current implementation in PHPLint.

v9.4.0 will come after I've finished https://github.com/llaville/sarif-php-converters and release the first version 1.0 with sarif-php-sdk 2.0

FormatResolver source code
<?php

declare(strict_types=1);


namespace Overtrue\PHPLint\Output;

use Overtrue\PHPLint\Configuration\OptionDefinition;
use Overtrue\PHPLint\Configuration\Resolver;
use Symfony\Component\Console\Input\InputInterface as SymfonyInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface as SymfonyConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface as SymfonyOutputInterface;
use Symfony\Component\Console\Output\StreamOutput;

use function array_key_exists;
use function array_merge;
use function class_exists;
use function fopen;

final class FormatResolver
{
    private const FORMATTERS = [
        'console' => ConsoleOutput::class,
        'json' => JsonOutput::class,
        'junit' => JunitOutput::class,
        'sarif' => SarifOutput::class,
    ];

    public function __construct(private readonly Resolver $configResolver)
    {
    }

    /**
     * @return OutputInterface[]
     */
    public function resolve(SymfonyInputInterface $input, SymfonyOutputInterface $output): array
    {
        $filename = $input->getOption('output');
        if ($filename) {
            $stream = fopen($filename, 'w');
        } else {
            $errOutput = $output instanceof SymfonyConsoleOutputInterface ? $output->getErrorOutput() : $output;
            if ($errOutput instanceof StreamOutput) {
                $stream = $errOutput->getStream();
            } else {
                $stream = fopen('php://stdout', 'w');
            }
        }

        $requestedFormats = array_merge($input->getOption('format'), $this->legacyLogOption());

        $handlers = [$output];

        foreach ($requestedFormats as $requestedFormat) {
            if ('console' === $requestedFormat) {
                // default behaviour
                continue;
            }

            if (array_key_exists($requestedFormat, self::FORMATTERS)) {
                // use built-in formatter
                $formatterClass = self::FORMATTERS[$requestedFormat];
                $handlers[] = new $formatterClass($stream, $output->getVerbosity(), $output->isDecorated(), $output->getFormatter());
                continue;
            }

            if (class_exists($requestedFormat)) {
                // try to load custom/external formatter
                $formatter = new $requestedFormat($stream, $output->getVerbosity(), $output->isDecorated(), $output->getFormatter());

                if (!$formatter instanceof OutputInterface) {
                    // skip invalid instance that does not implement contract
                    continue;
                }
                $handlers[] = $formatter;
            }
        }

        return $handlers;
    }

    /**
     * Checks if there is any `--log-[*]` legacy options
     *
     * @return string[]
     */
    private function legacyLogOption(): array
    {
        $outputOptions = [
            OptionDefinition::LOG_JSON => 'json',
            OptionDefinition::LOG_JUNIT => 'junit',
            OptionDefinition::LOG_SARIF => 'sarif',
        ];

        $requestedFormats = [];

        foreach ($outputOptions as $name => $format) {
            if ($this->configResolver->getOption($name)) {
                $requestedFormats[] = $format;
            }
        }

        return $requestedFormats;
    }
}

We will continue to support legacy options --log-[*] but we also add more generic

  -o, --output=OUTPUT                      Generate an output to the specified path (default: standard output)
      --format=FORMAT                      Format of requested reports (multiple values allowed)

Reference

Here are officially what I will support

Converter
Easy-Coding-Standard official website
Phan official website
PHP_CodeSniffer official website
PHP-CS-Fixer official website
PHPInsights official website
PHPLint official website
PHP Mess Detector official website
PHP Magic Number Detector official website
PHPStan official website

llaville avatar Jun 30 '24 08:06 llaville