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

[v9.4] Add support to more output formats #208

Closed
llaville opened this issue Jun 30, 2024 · 7 comments
Closed

[v9.4] Add support to more output formats #208

llaville opened this issue Jun 30, 2024 · 7 comments
Assignees

Comments

@llaville
Copy link
Collaborator

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 llaville self-assigned this Jun 30, 2024
@llaville
Copy link
Collaborator Author

llaville commented Jul 3, 2024

Now, the major version 2.0 of SARIF PHP SDK is available, I'm focus to prepare the first release of bartlett/sarif-php-converters that will support natively a reporter for PHPLint.

This is the reason, why PHPLint SarifOutput code should be upgrade before apply any dependency constraint (reason why I've declined #209)

@llaville
Copy link
Collaborator Author

llaville commented Jul 4, 2024

FYI @overtrue : I've just pushed code on new branch 9.4. This code is operational, and I hope to release it tomorrow, unless we found a big issue.

In summary:

  • sarif output format is now optional
  • added support to checkstyle format
  • legacy options log-* were removed
  • fix a little issue with launcher that did not detect short progress option usage

PS: see also examples https://github.com/overtrue/phplint/tree/9.4/examples/outputFormat

@overtrue
Copy link
Owner

overtrue commented Jul 4, 2024

it's OK 👍

@llaville
Copy link
Collaborator Author

llaville commented Jul 5, 2024

FYI @overtrue : I've just tested upcoming PHPLint 9.4.0 with bartlett/sarif-php-converters and upgrade its doc accordingly.

More details on how to use/print SARIF report with PHPLint may be found at https://github.com/llaville/sarif-php-converters/blob/1.0/docs/converter/phplint.md

@llaville
Copy link
Collaborator Author

llaville commented Jul 5, 2024

I should be able to publish the new version 9.4.0 stable (in state) later today.

@llaville
Copy link
Collaborator Author

llaville commented Jul 5, 2024

Here is an example of how to invoke PHPLint on command line, to print results in console, checkstyle and sarif format (human-readable with help of -v verbose mode level)

bin/phplint --format checkstyle -p meter  --no-cache --format console --format '\Overtrue\PHPLint\Output\SarifOutput'  --bootstrap /shared/backups/bartlett/sarif-php-converters/vendor/autoload.php  -v

Note

--bootstrap identify my local autoloader version of upcoming suppleant package bartlett/sarif-php-converters

phplint 9.4.0-dev by overtrue and contributors.

Runtime       : PHP 8.1.29
Configuration : /shared/backups/github/phplint/.phplint.yml

┌──────────────────┬─────────────────────────────────────────────────────────────────────┬─────────┐
│ Name             │ Value                                                               │ Help    │
├──────────────────┼─────────────────────────────────────────────────────────────────────┼─────────┤
│ path             │ ["src/","tests/"]                                                   │ command │
├──────────────────┼─────────────────────────────────────────────────────────────────────┼─────────┤
│ configuration    │ ".phplint.yml"                                                      │ command │
│ no-configuration │ false                                                               │ command │
│ exclude          │ ["vendor"]                                                          │ command │
│ extensions       │ ["php"]                                                             │ command │
│ jobs             │ 10                                                                  │ command │
│ no-cache         │ true                                                                │ command │
│ cache            │ ".phplint.cache"                                                    │ command │
│ no-progress      │ false                                                               │ command │
│ progress         │ "meter"                                                             │ command │
│ output           │ null                                                                │ command │
│ format           │ ["checkstyle","console","\\Overtrue\\PHPLint\\Output\\SarifOutput"] │ command │
│ warning          │ true                                                                │ command │
│ memory-limit     │ -1                                                                  │ command │
│ ignore-exit-code │ false                                                               │ command │
│ bootstrap        │ "/shared/backups/bartlett/sarif-php-converters/vendor/autoload.php" │ command │
└──────────────────┴─────────────────────────────────────────────────────────────────────┴─────────┘


Time: < 1 sec, Memory: 8.0 MiB, Cache: 0 hit, 54 misses, Processes: 54


 [ERROR] 54 files, 3 errors



There was 3 errors:
1. /shared/backups/github/phplint/tests/fixtures/syntax_error.php:4
    1| <?php
    2|
    3| print($a)
  > 4|
 unexpected end of file in line 4
2. /shared/backups/github/phplint/tests/fixtures/php-8.2_syntax.php:12
     9|  * with this source code in the file LICENSE.
    10|  */
    11|
  > 12| function alwaysReturnsFalse(): false
    13| {
    14| }
    15|
 False can not be used as a standalone type in line 12
3. /shared/backups/github/phplint/tests/fixtures/syntax_warning.php:12
     9|  * with this source code in the file LICENSE.
    10|  */
    11|
  > 12| declare(encoding="utf8");
    13|
  declare(encoding=...) ignored because Zend multibyte feature is turned off by settings in line 12
{
    "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
    "version": "2.1.0",
    "runs": [
        {
            "tool": {
                "driver": {
                    "name": "PHPLint",
                    "shortDescription": {
                        "text": "Syntax check only (lint) of PHP files"
                    },
                    "fullDescription": {
                        "text": "PHPLint is a tool that can speed up linting of php files by running several lint processes at once."
                    },
                    "fullName": "PHPLint version 9.4.9999999.9999999-dev by overtrue and contributors",
                    "semanticVersion": "9.4.9999999.9999999-dev",
                    "informationUri": "https://github.com/overtrue/phplint",
                    "rules": [
                        {
                            "id": "PHPLINT101",
                            "shortDescription": {
                                "text": "Syntax error"
                            },
                            "fullDescription": {
                                "text": "Syntax error detected when lint a file"
                            },
                            "helpUri": "https://www.php.net/manual/en/langref.php",
                            "help": {
                                "text": "https://www.php.net/manual/en/features.commandline.options.php"
                            }
                        }
                    ]
                },
                "extensions": [
                    {
                        "name": "bartlett/sarif-php-converters",
                        "shortDescription": {
                            "text": "PHPLint SARIF Converter"
                        },
                        "version": "1.0.9999999.9999999-dev"
                    }
                ]
            },
            "invocations": [
                {
                    "executionSuccessful": true,
                    "commandLine": "bin/phplint",
                    "arguments": [
                        "--format",
                        "checkstyle",
                        "-p",
                        "meter",
                        "--no-cache",
                        "--format",
                        "console",
                        "--format",
                        "\\Overtrue\\PHPLint\\Output\\SarifOutput",
                        "--bootstrap",
                        "/shared/backups/bartlett/sarif-php-converters/vendor/autoload.php",
                        "-v"
                    ],
                    "workingDirectory": {
                        "uri": "file:///shared/backups/github/phplint/"
                    }
                }
            ],
            "originalUriBaseIds": {
                "WORKINGDIR": {
                    "uri": "file:///shared/backups/github/phplint/"
                }
            },
            "results": [
                {
                    "message": {
                        "text": "unexpected end of file in line 4"
                    },
                    "ruleId": "PHPLINT101",
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "tests/fixtures/syntax_error.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 4,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m4| \u001b[0m"
                                        }
                                    }
                                },
                                "contextRegion": {
                                    "startLine": 2,
                                    "endLine": 6,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m2| \u001b[0m\n    \u001b[90m3| \u001b[0m\u001b[32mprint(\u001b[0m\u001b[39m$a\u001b[0m\u001b[32m)\u001b[0m\n    \u001b[90m4| \u001b[0m"
                                        }
                                    }
                                }
                            }
                        }
                    ],
                    "partialFingerprints": {
                        "PHPLINT101": "9d2c5cee410c5007acb62ee25b9a0dfb740fb8f531235e6abc5dd7535930ef2f"
                    }
                },
                {
                    "message": {
                        "text": "False can not be used as a standalone type in line 12"
                    },
                    "ruleId": "PHPLINT101",
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "tests/fixtures/php-8.2_syntax.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 12,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m12| \u001b[0m\u001b[32mfunction \u001b[0m\u001b[39malwaysReturnsFalse\u001b[0m\u001b[32m(): \u001b[0m\u001b[39mfalse\u001b[0m"
                                        }
                                    }
                                },
                                "contextRegion": {
                                    "startLine": 10,
                                    "endLine": 14,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m10| \u001b[0m\u001b[33m */\u001b[0m\n    \u001b[90m11| \u001b[0m\n    \u001b[90m12| \u001b[0m\u001b[32mfunction \u001b[0m\u001b[39malwaysReturnsFalse\u001b[0m\u001b[32m(): \u001b[0m\u001b[39mfalse\u001b[0m\n    \u001b[90m13| \u001b[0m\u001b[32m{\u001b[0m\n    \u001b[90m14| \u001b[0m\u001b[32m}\u001b[0m"
                                        }
                                    }
                                }
                            }
                        }
                    ],
                    "partialFingerprints": {
                        "PHPLINT101": "b4f5ba1d66790be578109d251ced990b42fe6117554a275142ab750f50ca39f4"
                    }
                },
                {
                    "message": {
                        "text": " declare(encoding=...) ignored because Zend multibyte feature is turned off by settings in line 12"
                    },
                    "ruleId": "PHPLINT101",
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "tests/fixtures/syntax_warning.php",
                                    "uriBaseId": "WORKINGDIR"
                                },
                                "region": {
                                    "startLine": 12,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m12| \u001b[0m\u001b[32mdeclare(\u001b[0m\u001b[39mencoding\u001b[0m\u001b[32m=\u001b[0m\u001b[31m\"utf8\"\u001b[0m\u001b[32m);\u001b[0m"
                                        }
                                    }
                                },
                                "contextRegion": {
                                    "startLine": 10,
                                    "endLine": 14,
                                    "snippet": {
                                        "rendered": {
                                            "text": "\u001b[31m  > \u001b[0m\u001b[90m10| \u001b[0m\u001b[33m */\u001b[0m\n    \u001b[90m11| \u001b[0m\n    \u001b[90m12| \u001b[0m\u001b[32mdeclare(\u001b[0m\u001b[39mencoding\u001b[0m\u001b[32m=\u001b[0m\u001b[31m\"utf8\"\u001b[0m\u001b[32m);\u001b[0m\n    \u001b[90m13| \u001b[0m"
                                        }
                                    }
                                }
                            }
                        }
                    ],
                    "partialFingerprints": {
                        "PHPLINT101": "a1bed88116ad4e69c924107f5fa77a80379a08f2723871f5d0af6eb272dcf3c2"
                    }
                }
            ],
            "automationDetails": {
                "id": "Daily run 2024-07-05T03:49:55+00:00"
            }
        }
    ]
}
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle>
  <file name="/shared/backups/github/phplint/tests/fixtures/syntax_error.php">
    <error line="4" severity="error" message="unexpected end of file in line 4"/>
  </file>
  <file name="/shared/backups/github/phplint/tests/fixtures/php-8.2_syntax.php">
    <error line="12" severity="error" message="False can not be used as a standalone type in line 12"/>
  </file>
  <file name="/shared/backups/github/phplint/tests/fixtures/syntax_warning.php">
    <error line="12" severity="error" message=" declare(encoding=...) ignored because Zend multibyte feature is turned off by settings in line 12"/>
  </file>
</checkstyle>

@llaville
Copy link
Collaborator Author

llaville commented Jul 5, 2024

Available on official release 9.4.0

@llaville llaville closed this as completed Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants