overtrue / phplint

:bug: A tool that can speed up linting of php files by running several lint processes at once.
MIT License
972 stars 118 forks source link

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

Closed llaville closed 1 day ago

llaville commented 6 days ago

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 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 commented 3 days ago

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 https://github.com/overtrue/phplint/pull/209)

llaville commented 2 days ago

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:

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

overtrue commented 2 days ago

it's OK πŸ‘

llaville commented 1 day ago

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 commented 1 day ago

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

llaville commented 1 day ago

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 commented 1 day ago

Available on official release 9.4.0