diff --git a/.phplint.yml b/.phplint.yml index b2bca65c..f22953e1 100644 --- a/.phplint.yml +++ b/.phplint.yml @@ -9,6 +9,3 @@ exclude: warning: true memory-limit: -1 no-cache: false -log-json: false -log-junit: false -log-sarif: false \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 039a5ac2..d084dd07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ USER appuser # Install Composer v2 then overtrue/phplint package COPY --from=composer/composer:2-bin /composer /usr/bin/composer ENV COMPOSER_ALLOW_SUPERUSER 1 -RUN composer global require --no-progress overtrue/phplint 9.3.x-dev +RUN composer global require --no-progress overtrue/phplint 9.4.x-dev # Following recommendation at https://docs.github.com/en/actions/creating-actions/dockerfile-support-for-github-actions#workdir diff --git a/README.md b/README.md index 516c4eb7..20a119a7 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ | Version | Status | Requirements | |:--------|:------------------------------------------|:---------------| | **9.x** | **Active development** | **PHP >= 8.1** | +| 9.4 | Active support | PHP >= 8.1 | | 9.3 | Active support | PHP >= 8.1 | | 9.2 | Active support | PHP >= 8.1 | | 9.1 | Active support | PHP >= 8.1 | diff --git a/docs/installation.md b/docs/installation.md index 57e145df..7f98c2bc 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -48,7 +48,7 @@ You can also install `phplint` locally to your project with [Phive][phive] and c ```xml - + ``` diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 00000000..d3672ba6 --- /dev/null +++ b/src/Client.php @@ -0,0 +1,30 @@ +application; + } +} diff --git a/src/Command/ConfigureCommandTrait.php b/src/Command/ConfigureCommandTrait.php index 7d1ab693..30db69a4 100644 --- a/src/Command/ConfigureCommandTrait.php +++ b/src/Command/ConfigureCommandTrait.php @@ -88,28 +88,16 @@ protected function configureCommand(Command $command): void 'Hide the progress output' ) ->addOption( - 'log-json', - null, - InputOption::VALUE_OPTIONAL, - 'Log scan results in JSON format to file (default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . ')' - ) - ->addOption( - 'log-junit', - null, - InputOption::VALUE_OPTIONAL, - 'Log scan results in JUnit XML format to file (default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . ')' - ) - ->addOption( - 'log-sarif', - null, - InputOption::VALUE_OPTIONAL, - 'Log scan results in SARIF format to file (default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . ')' + 'output', + 'o', + InputOption::VALUE_REQUIRED, + 'Generate an output to the specified path (default: ' . OptionDefinition::DEFAULT_STANDARD_OUTPUT_LABEL . ')' ) ->addOption( - 'sarif-converter', + 'format', null, - InputOption::VALUE_OPTIONAL, - 'SARIF class converter (default: ' . OptionDefinition::DEFAULT_SARIF_CONVERTER_CLASS . ')' + InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, + 'Format of requested reports' ) ->addOption( 'warning', diff --git a/src/Command/LintCommand.php b/src/Command/LintCommand.php index 43cdb7ac..f223bd09 100644 --- a/src/Command/LintCommand.php +++ b/src/Command/LintCommand.php @@ -13,6 +13,7 @@ namespace Overtrue\PHPLint\Command; +use Overtrue\PHPLint\Client; use Overtrue\PHPLint\Configuration\ConsoleOptionsResolver; use Overtrue\PHPLint\Configuration\FileOptionsResolver; use Overtrue\PHPLint\Configuration\OptionDefinition; @@ -86,9 +87,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $finder = (new Finder($configResolver))->getFiles(); - /** @var Application $app */ - $app = $this->getApplication(); - $linter = new Linter($configResolver, $this->dispatcher, $app->getLongVersion(), $this->getHelperSet(), $output); + $linter = new Linter( + $configResolver, + $this->dispatcher, + new Client($this->getApplication()), + $this->getHelperSet(), + $output + ); $this->results = $linter->lintFiles($finder, $startTime); $data = $this->results->getFailures(); diff --git a/src/Configuration/ConsoleOptionsResolver.php b/src/Configuration/ConsoleOptionsResolver.php index 4add9919..515ea7db 100644 --- a/src/Configuration/ConsoleOptionsResolver.php +++ b/src/Configuration/ConsoleOptionsResolver.php @@ -17,7 +17,7 @@ * @author Laurent Laville * @since Release 9.0.0 */ -class ConsoleOptionsResolver extends AbstractOptionsResolver +final class ConsoleOptionsResolver extends AbstractOptionsResolver { public function factory(): Options { diff --git a/src/Configuration/FileOptionsResolver.php b/src/Configuration/FileOptionsResolver.php index d3410f61..53169568 100644 --- a/src/Configuration/FileOptionsResolver.php +++ b/src/Configuration/FileOptionsResolver.php @@ -25,7 +25,7 @@ * @author Laurent Laville * @since Release 9.0.0 */ -class FileOptionsResolver extends AbstractOptionsResolver +final class FileOptionsResolver extends AbstractOptionsResolver { public function __construct(InputInterface $input) { diff --git a/src/Console/Application.php b/src/Console/Application.php index 44a32868..6508aaf3 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -16,19 +16,19 @@ use Overtrue\PHPLint\Helper\DebugFormatterHelper; use Overtrue\PHPLint\Helper\ProcessHelper; use Overtrue\PHPLint\Output\ConsoleOutput; -use Phar; use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use function array_keys; use function in_array; +use const STDOUT; + /** * @author Overtrue * @author Laurent Laville (since v9.0) @@ -36,7 +36,7 @@ final class Application extends BaseApplication { public const NAME = 'phplint'; - public const VERSION = '9.3.1'; + public const VERSION = '9.4.0-dev'; public function __construct() { @@ -45,27 +45,11 @@ public function __construct() public function run(InputInterface $input = null, OutputInterface $output = null): int { - $output ??= new ConsoleOutput(); + $output ??= new ConsoleOutput(STDOUT); return parent::run($input, $output); } - protected function configureIO(InputInterface $input, OutputInterface $output): void - { - if (Phar::running()) { - $inputDefinition = $this->getDefinition(); - $inputDefinition->addOption( - new InputOption( - 'manifest', - null, - InputOption::VALUE_NONE, - 'Show which versions of dependencies are bundled' - ) - ); - } - parent::configureIO($input, $output); - } - protected function getDefaultCommands(): array { return [new HelpCommand(), new ListCommand()]; diff --git a/src/Linter.php b/src/Linter.php index c5f82b51..dfc63560 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -21,7 +21,6 @@ use Overtrue\PHPLint\Event\BeforeCheckingEvent; use Overtrue\PHPLint\Event\BeforeLintFileEvent; use Overtrue\PHPLint\Helper\ProcessHelper; -use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Overtrue\PHPLint\Output\LinterOutput; use Overtrue\PHPLint\Process\LintProcess; use Psr\Cache\InvalidArgumentException; @@ -40,6 +39,7 @@ use function md5_file; use function microtime; use function phpversion; +use function strip_tags; use function version_compare; /** @@ -57,20 +57,16 @@ final class Linter private int $processLimit; private string $memoryLimit; private bool $warning; - private array $options; - private string $appLongVersion; public function __construct( Resolver $configResolver, EventDispatcherInterface $dispatcher, - string $appVersion = '9.1.x-dev', - HelperSet $helperSet = null, - OutputInterface $output = null + private readonly ?Client $client = null, + ?HelperSet $helperSet = null, + ?OutputInterface $output = null, ) { $this->configResolver = $configResolver; $this->dispatcher = $dispatcher; - $this->appLongVersion = $appVersion; - $this->options = $configResolver->getOptions(); $this->processLimit = $configResolver->getOption(OptionDefinition::JOBS); $this->memoryLimit = (string) $configResolver->getOption(OptionDefinition::OPTION_MEMORY_LIMIT); $this->warning = $configResolver->getOption(OptionDefinition::WARNING); @@ -108,21 +104,7 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu $fileCount = 0; } - if ($this->output instanceof ConsoleOutputInterface) { - $configFile = $this->options['no-configuration'] - ? '' - : $this->options['configuration'] - ; - $this->output->headerBlock($this->appLongVersion, $configFile); - $this->output->configBlock($this->options); - } - - $this->dispatcher->dispatch( - new BeforeCheckingEvent( - $this, - ['fileCount' => $fileCount, 'appVersion' => $this->appLongVersion, 'options' => $this->options] - ) - ); + $this->dispatcher->dispatch(new BeforeCheckingEvent($this, ['fileCount' => $fileCount])); $processCount = 0; if ($fileCount > 0) { @@ -131,8 +113,16 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu $results = []; } + if (null !== $this->client) { + $default = [ + 'application_version' => [ + 'long' => $this->client->getApplication()->getLongVersion(), + 'short' => $this->client->getApplication()->getVersion(), + ] + ]; + } $finalResults = new LinterOutput($results, $finder); - $finalResults->setContext($this->configResolver, $startTime, $processCount); + $finalResults->setContext($this->configResolver, $startTime, $processCount, $default ?? []); $this->dispatcher->dispatch(new AfterCheckingEvent($this, ['results' => $finalResults])); diff --git a/src/Output/ChainOutput.php b/src/Output/ChainOutput.php index e512992e..ee4347c0 100644 --- a/src/Output/ChainOutput.php +++ b/src/Output/ChainOutput.php @@ -16,6 +16,7 @@ use InvalidArgumentException; use function count; +use function fclose; use function get_debug_type; use function sprintf; @@ -45,12 +46,24 @@ public function __construct(array $handlers) } } + public function getName(): string + { + return 'chain'; + } + public function format(LinterOutput $results): void { $i = count($this->outputHandlers); + if ($i === 0) { + return; + } + while ($i--) { $this->outputHandlers[$i]->format($results); } + + // close stream only once all formatters do their job + fclose($this->outputHandlers[0]->getStream()); } } diff --git a/src/Output/ConsoleOutput.php b/src/Output/ConsoleOutput.php index db6fe885..0aa4e4b0 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/ConsoleOutput.php @@ -21,7 +21,8 @@ use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\ConsoleOutput as BaseConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput; +use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Terminal; use Symfony\Component\Finder\SplFileInfo; @@ -56,7 +57,7 @@ * @author Laurent Laville * @since Release 9.0.0 */ -class ConsoleOutput extends BaseConsoleOutput implements ConsoleOutputInterface +final class ConsoleOutput extends StreamOutput implements OutputInterface, ConsoleOutputInterface { public const MAX_LINE_LENGTH = 120; @@ -64,13 +65,22 @@ class ConsoleOutput extends BaseConsoleOutput implements ConsoleOutputInterface private int $lineLength; - public function __construct(int $verbosity = parent::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) - { - parent::__construct($verbosity, $decorated, $formatter); + public function __construct( + $stream, + int $verbosity = parent::VERBOSITY_NORMAL, + ?bool $decorated = null, + ?OutputFormatterInterface $formatter = null + ) { + parent::__construct($stream, $verbosity, $decorated, $formatter); $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); } + public function getName(): string + { + return 'console'; + } + public function format(LinterOutput $results): void { $data = $results->getFailures(); @@ -83,6 +93,11 @@ public function format(LinterOutput $results): void return; } + $options = $context['options_used'] ?? []; + $configFile = $options['no-configuration'] ? '' : $options['configuration']; + $this->headerBlock($context['application_version']['long'], $configFile); + $this->configBlock($options); + $this->consumeBlock($context['time_usage'], $context['memory_usage'], $context['cache_usage'], $context['process_count']); if ($errCount > 0) { @@ -262,8 +277,7 @@ public function configBlock(array $options): void } } - $section = $this->section(); - $table = new Table($section); + $table = new Table($this); $table ->setHeaders($headers) ->setRows($rows) diff --git a/src/Output/ConsoleOutputInterface.php b/src/Output/ConsoleOutputInterface.php index 41d38b98..07a35a8a 100644 --- a/src/Output/ConsoleOutputInterface.php +++ b/src/Output/ConsoleOutputInterface.php @@ -14,13 +14,14 @@ namespace Overtrue\PHPLint\Output; use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface as SymfonyOutputInterface; use Symfony\Component\Finder\SplFileInfo; /** * @author Laurent Laville * @since Release 9.1.0 */ -interface ConsoleOutputInterface extends OutputInterface +interface ConsoleOutputInterface extends SymfonyOutputInterface { public const NO_FILE_TO_LINT = 'Could not find any files to lint'; diff --git a/src/Output/LinterOutput.php b/src/Output/LinterOutput.php index d6a92e15..e19baf7c 100644 --- a/src/Output/LinterOutput.php +++ b/src/Output/LinterOutput.php @@ -57,7 +57,7 @@ public function getContext(): array return $this->context; } - public function setContext(Resolver $configResolver, float $startTime, int $processCount): void + public function setContext(Resolver $configResolver, float $startTime, int $processCount, array $default = []): void { $cacheHits = count($this->getHits()); $cacheMisses = count($this->getMisses()); @@ -78,14 +78,14 @@ public function setContext(Resolver $configResolver, float $startTime, int $proc $fileCount = 0; } - $this->context = [ + $this->context = array_merge($default, [ 'time_usage' => $timeUsage, 'memory_usage' => $memUsage, 'cache_usage' => $cacheUsage, 'process_count' => $processCount, 'files_count' => $fileCount, 'options_used' => $configResolver->getOptions(), - ]; + ]); } public function hasFailures(): bool diff --git a/src/Output/OutputInterface.php b/src/Output/OutputInterface.php index ce36ace6..3d7aab27 100644 --- a/src/Output/OutputInterface.php +++ b/src/Output/OutputInterface.php @@ -19,5 +19,8 @@ */ interface OutputInterface { + public function getName(): string; + public function format(LinterOutput $results): void; + } diff --git a/tests/Configuration/ConsoleConfigTest.php b/tests/Configuration/ConsoleConfigTest.php index a94865b7..f0422375 100644 --- a/tests/Configuration/ConsoleConfigTest.php +++ b/tests/Configuration/ConsoleConfigTest.php @@ -68,18 +68,10 @@ public static function commandInputProvider(): array 'multiple path modified' => [['path' => [dirname(__DIR__) . '/Cache', __DIR__]], __CLASS__ . '::expectedPathModified'], 'without external configuration' => [['--no-configuration' => true], __CLASS__ . '::expectedExternalConfigNotFetched'], 'with external empty configuration' => [['--configuration' => 'tests/Configuration/empty.yaml'], __CLASS__ . '::expectedExternalEmptyConfig'], - 'output to JSON format on Stdout 1/3' => [['--log-json' => null], __CLASS__ . '::expectedJsonOutputFormat'], - 'output to JSON format on Stdout 2/3' => [['--log-json' => ''], __CLASS__ . '::expectedJsonOutputFormat'], - 'output to JSON format on Stdout 3/3' => [['--log-json' => true], __CLASS__ . '::expectedJsonOutputFormat'], - 'disable output to JSON format 1/2' => [['--log-json' => false], __CLASS__ . '::expectedJsonOutputFormat'], - 'disable output to JSON format 2/2' => [['--log-json' => 'off'], __CLASS__ . '::expectedJsonOutputFormat'], - 'output to JSON format on File' => [['--log-json' => '/tmp/phplint.json'], __CLASS__ . '::expectedJsonOutputFormat'], - 'output to XML format on Stdout 1/3' => [['--log-junit' => null], __CLASS__ . '::expectedXmlOutputFormat'], - 'output to XML format on Stdout 2/3' => [['--log-junit' => ''], __CLASS__ . '::expectedXmlOutputFormat'], - 'output to XML format on Stdout 3/3' => [['--log-junit' => true], __CLASS__ . '::expectedXmlOutputFormat'], - 'disable output to XML format 1/2' => [['--log-junit' => false], __CLASS__ . '::expectedXmlOutputFormat'], - 'disable output to XML format 2/2' => [['--log-junit' => 'FALSE'], __CLASS__ . '::expectedXmlOutputFormat'], - 'output to XML format on File' => [['--log-junit' => '/tmp/phplint.xml'], __CLASS__ . '::expectedXmlOutputFormat'], + 'output to JSON format on Stdout' => [['--format' => 'json'], __CLASS__ . '::expectedJsonOutputFormat'], + 'output to JSON format on File' => [['--format' => 'json', '--output' => '/tmp/phplint.json'], __CLASS__ . '::expectedJsonOutputFormat'], + 'output to XML format on Stdout' => [['--format' => 'junit'], __CLASS__ . '::expectedXmlOutputFormat'], + 'output to XML format on File' => [['--format' => 'junit', '--output' => '/tmp/phplint.xml'], __CLASS__ . '::expectedXmlOutputFormat'], ]; } @@ -107,18 +99,12 @@ protected static function expectedExternalEmptyConfig(Resolver $resolver, array protected static function expectedJsonOutputFormat(Resolver $resolver, array $arguments): array { - $expected = self::getExpectedValues($resolver); - $logJson = $arguments['--log-json']; - $expected['log-json'] = OptionsFactory::logNormalizer(new OptionsResolver(), $logJson); - return $expected; + return self::getExpectedValues($resolver); // expected only default arguments/options from command line } protected static function expectedXmlOutputFormat(Resolver $resolver, array $arguments): array { - $expected = self::getExpectedValues($resolver); - $logJunit = $arguments['--log-junit']; - $expected['log-junit'] = OptionsFactory::logNormalizer(new OptionsResolver(), $logJunit); - return $expected; + return self::getExpectedValues($resolver); // expected only default arguments/options from command line } protected static function getExpectedValues(Resolver $resolver): array diff --git a/tests/Configuration/YamlConfigTest.php b/tests/Configuration/YamlConfigTest.php index 977e6e72..d3d3fa9c 100644 --- a/tests/Configuration/YamlConfigTest.php +++ b/tests/Configuration/YamlConfigTest.php @@ -93,14 +93,16 @@ protected static function expectedJobsModified(Resolver $resolver): array protected static function expectedJsonOutputFormat(Resolver $resolver, array $arguments): array { $expected = self::getExpectedValues($resolver); - $expected['log-json'] = OptionDefinition::DEFAULT_STANDARD_OUTPUT; // see 'log-json.yaml' contents + $expected['output'] = OptionDefinition::DEFAULT_STANDARD_OUTPUT; // see 'log-json.yaml' contents + $expected['format'] = ['json']; return $expected; } protected static function expectedXmlOutputFormat(Resolver $resolver, array $arguments): array { $expected = self::getExpectedValues($resolver); - $expected['log-junit'] = '/tmp/phplint-results.xml'; // see 'log-junit.yaml' contents + $expected['output'] = '/tmp/phplint-results.xml'; // see 'log-junit.yaml' contents + $expected['format'] = ['junit']; return $expected; } diff --git a/tests/Configuration/log-json.yaml b/tests/Configuration/log-json.yaml index 132c6521..aec4d2c4 100644 --- a/tests/Configuration/log-json.yaml +++ b/tests/Configuration/log-json.yaml @@ -1 +1,3 @@ -log-json: php://stdout +output: php://stdout +format: + - json diff --git a/tests/Configuration/log-junit.yaml b/tests/Configuration/log-junit.yaml index 4b30aed8..0a5984da 100644 --- a/tests/Configuration/log-junit.yaml +++ b/tests/Configuration/log-junit.yaml @@ -1 +1,3 @@ -log-junit: /tmp/phplint-results.xml +output: /tmp/phplint-results.xml +format: + - junit