From 935c1fce9997325a966539f92904a18b221429fe Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 15:18:10 +0000 Subject: [PATCH 01/53] add new benchmark GitHub Workflows to compare performance between PHP 8.1 and 8.3 --- .github/workflows/benchmark.yml | 125 ++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..fbe999ed --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,125 @@ +--- +name: Benchmarking + +on: + workflow_dispatch: + +jobs: + benchmark-baseline: + name: "Benchmark Baseline" + + runs-on: "${{ matrix.operating-system }}" + + strategy: + fail-fast: false + + matrix: + operating-system: + - "ubuntu-22.04" + + php-version: + - "8.1" + + steps: + - # https://github.com/actions/checkout + name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: "9.1" + repository: "overtrue/phplint" + + - # https://github.com/shivammathur/setup-php + name: Setup PHP runtime + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: "none" + + - # https://github.com/ramsey/composer-install + name: Install Composer dependencies + uses: ramsey/composer-install@v2 + with: + composer-options: "--prefer-dist" + + - # https://github.com/phpbench/phpbench + name: Install PHPBench + run: | + curl -Ls https://github.com/phpbench/phpbench/releases/download/1.2.15/phpbench.phar -o /usr/local/bin/phpbench + chmod +x /usr/local/bin/phpbench + + - # https://github.com/phpbench/phpbench + name: Benchmark baseline + run: | + phpbench run tests/Benchmark --tag=${{ matrix.php-version }} + + - # https://github.com/actions/upload-artifact + name: Upload PHPBench baseline + uses: actions/upload-artifact@v3 + with: + name: "PHPBench-Baseline" + path: ".phpbench/" + + benchmark-report: + needs: benchmark-baseline + + name: "Benchmark Report" + + runs-on: "${{ matrix.operating-system }}" + + strategy: + fail-fast: false + + matrix: + operating-system: + - "ubuntu-22.04" + + php-version: + - "8.3" + + steps: + - # https://github.com/actions/checkout + name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: "9.1" + repository: "overtrue/phplint" + + - # https://github.com/shivammathur/setup-php + name: Setup PHP runtime + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php-version }}" + coverage: "none" + + - # https://github.com/ramsey/composer-install + name: Install Composer dependencies + uses: ramsey/composer-install@v2 + with: + composer-options: "--prefer-dist" + + - # https://github.com/phpbench/phpbench + name: Install PHPBench + run: | + curl -Ls https://github.com/phpbench/phpbench/releases/download/1.2.15/phpbench.phar -o /usr/local/bin/phpbench + chmod +x /usr/local/bin/phpbench + + - # https://github.com/actions/download-artifact + name: Retrieve PHPBench baseline results + uses: actions/download-artifact@v3 + with: + name: "PHPBench-Baseline" + path: ".phpbench/" + + - # https://github.com/phpbench/phpbench + name: Benchmark Reports + run: | + phpbench run tests/Benchmark --tag=${{ matrix.php-version }} --ref="8.1" --report=aggregate --report overview --output=html + + - # https://github.com/actions/upload-artifact + name: Upload PHPBench report + uses: actions/upload-artifact@v3 + with: + name: "PHPBench-Report" + path: ".phpbench/html/index.html" From 658918e301541c4a242c7cec2e7ff4be4cb77e5b Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 2 Dec 2023 08:54:10 +0000 Subject: [PATCH 02/53] upgrade schema of PHPUnit 10 and drop support of PHPUnit 9 --- .../unreleased/Removed-20231202-085629.yaml | 3 ++ .gitignore | 1 - phpunit-9.xml | 37 ------------------- phpunit.xml | 2 +- vendor-bin/phpunit/composer.json | 2 +- 5 files changed, 5 insertions(+), 40 deletions(-) create mode 100644 .changes/unreleased/Removed-20231202-085629.yaml delete mode 100644 phpunit-9.xml diff --git a/.changes/unreleased/Removed-20231202-085629.yaml b/.changes/unreleased/Removed-20231202-085629.yaml new file mode 100644 index 00000000..62790487 --- /dev/null +++ b/.changes/unreleased/Removed-20231202-085629.yaml @@ -0,0 +1,3 @@ +kind: Removed +body: drop support of PHPUnit 9 +time: 2023-12-02T08:56:29.094840086Z diff --git a/.gitignore b/.gitignore index 4ea98549..666323ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ composer.lock /vendor/ .phpunit.cache/ -.phpunit.result.cache .phplint.cache/ .idea .php-cs-fixer.cache diff --git a/phpunit-9.xml b/phpunit-9.xml deleted file mode 100644 index 868c8059..00000000 --- a/phpunit-9.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - tests/Cache - - - tests/Configuration - - - tests/EndToEnd - - - tests/Finder - - - - - - src - - - diff --git a/phpunit.xml b/phpunit.xml index 10f7f09e..5c371142 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,6 +1,6 @@ Date: Sat, 2 Dec 2023 09:21:40 +0000 Subject: [PATCH 03/53] PHP 8.0 support was dropped --- .changes/unreleased/Removed-20231202-090942.yaml | 3 +++ .github/workflows/lint.yml | 15 --------------- composer.json | 4 ++-- docs/installation.md | 4 ++-- src/Console/Application.php | 2 +- src/Linter.php | 2 +- 6 files changed, 9 insertions(+), 21 deletions(-) create mode 100644 .changes/unreleased/Removed-20231202-090942.yaml diff --git a/.changes/unreleased/Removed-20231202-090942.yaml b/.changes/unreleased/Removed-20231202-090942.yaml new file mode 100644 index 00000000..5a1fa0c8 --- /dev/null +++ b/.changes/unreleased/Removed-20231202-090942.yaml @@ -0,0 +1,3 @@ +kind: Removed +body: drop support of PHP 8.0 +time: 2023-12-02T09:09:42.469702494Z diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 926e1569..afed679b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,6 @@ jobs: - "ubuntu-22.04" php-version: - - "8.0" - "8.1" - "8.2" - "8.3" @@ -30,17 +29,8 @@ jobs: name: Checkout code uses: actions/checkout@v4 - - # https://github.com/shivammathur/setup-php - name: Setup PHP runtime for PHPUnit 9 - uses: shivammathur/setup-php@v2 - if: ${{ matrix.php-version == '8.0' }} - with: - php-version: "${{ matrix.php-version }}" - tools: phpunit:9.6 - - # https://github.com/shivammathur/setup-php name: Setup PHP runtime for PHPUnit 10 - if: ${{ matrix.php-version != '8.0' }} uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php-version }}" @@ -52,11 +42,6 @@ jobs: with: composer-options: "--prefer-dist --no-scripts" - - # https://github.com/sebastianbergmann/phpunit/tree/9.6 - name: Unit tests with PHPUnit 9 - if: ${{ matrix.php-version == '8.0' }} - run: phpunit --configuration ./phpunit-9.xml --testdox --do-not-cache-result - # https://github.com/sebastianbergmann/phpunit/tree/10.5 name: Unit tests with PHPUnit 10 - if: ${{ matrix.php-version != '8.0' }} run: phpunit --no-progress --testdox --do-not-cache-result diff --git a/composer.json b/composer.json index a6c05732..9698953b 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", "ext-mbstring": "*", "symfony/cache": "^5.4 || ^6.0 || ^7.0", @@ -55,7 +55,7 @@ ] }, "branch-alias": { - "dev-main": "9.0.x-dev" + "dev-main": "9.1.x-dev" } }, "scripts": { diff --git a/docs/installation.md b/docs/installation.md index 7860ba25..c95605e0 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -13,7 +13,7 @@ | **9.x** | **Active development** | **PHP >= 8.0** | | 6.x | Active support | PHP >= 8.2 | | 5.x | Active support | PHP >= 8.1 | -| 4.x | Active support | PHP >= 8.0 | +| 4.x | End Of Life | PHP >= 8.0 | | 3.x | End Of Life | PHP >= 7.4 | ## PHAR @@ -48,7 +48,7 @@ You can also install `phplint` locally to your project with [Phive][phive] and c ```xml - + ``` diff --git a/src/Console/Application.php b/src/Console/Application.php index 5fe73826..036f7a5c 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -33,7 +33,7 @@ final class Application extends BaseApplication { public const NAME = 'phplint'; - public const VERSION = '9.0.6'; + public const VERSION = '9.1.0-dev'; public function __construct() { diff --git a/src/Linter.php b/src/Linter.php index 5dfe03a3..d997744f 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -49,7 +49,7 @@ final class Linter private array $options; private string $appLongVersion; - public function __construct(Resolver $configResolver, EventDispatcherInterface $dispatcher, string $appVersion = '9.0.x-dev') + public function __construct(Resolver $configResolver, EventDispatcherInterface $dispatcher, string $appVersion = '9.1.x-dev') { $this->configResolver = $configResolver; $this->dispatcher = $dispatcher; From c1b27d01fc304c64715a3ee200589ba2ca2fb7d1 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 2 Dec 2023 09:29:25 +0000 Subject: [PATCH 04/53] update project page about new 9.1.x future releases --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 01d20af8..26b21907 100644 --- a/README.md +++ b/README.md @@ -12,14 +12,17 @@ | Version | Status | Requirements | |:--------|:------------------------------------------|:---------------| -| **9.x** | **Active development** | **PHP >= 8.0** | +| **9.1** | **Active development** | **PHP >= 8.1** | +| 9.0 | Active support | PHP >= 8.0 | | 6.x | Active support | PHP >= 8.2 | | 5.x | Active support | PHP >= 8.1 | -| 4.x | Active support | PHP >= 8.0 | +| 4.x | End Of Life | PHP >= 8.0 | | 3.x | End Of Life | PHP >= 7.4 | **NOTE** if you have an older version of PHP lower than 8.0, we recommend to use the latest version [3.4.0](https://github.com/overtrue/phplint/releases/tag/3.4.0) +Version 9.1 is based on 9.0 code but did not support anymore PHP 8.0 that reached End-Of-Life. + Major version 9.0 is a full code rewrites that have the same source code (`main` branch) for all PHP 8.x versions (`4.x`, `5.x` or `6.x`), with lot of improvement, full documentation, and full unit code tested. From a9bec519f7ad3ccfcac981fbf32f8afec370c94e Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 2 Dec 2023 09:49:41 +0000 Subject: [PATCH 05/53] replaced old Symfony LTS by new one (read more at https://symfony.com/releases) --- .changes/unreleased/Changed-20231202-094819.yaml | 4 ++++ composer.json | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 .changes/unreleased/Changed-20231202-094819.yaml diff --git a/.changes/unreleased/Changed-20231202-094819.yaml b/.changes/unreleased/Changed-20231202-094819.yaml new file mode 100644 index 00000000..ea2d7381 --- /dev/null +++ b/.changes/unreleased/Changed-20231202-094819.yaml @@ -0,0 +1,4 @@ +kind: Changed +body: Replaces Symfony components constraint to new LTS (6.4), and drop support to + old one (5.4) +time: 2023-12-02T09:48:19.920649128Z diff --git a/composer.json b/composer.json index 9698953b..77e5fec6 100644 --- a/composer.json +++ b/composer.json @@ -24,13 +24,13 @@ "php": "^8.1", "ext-json": "*", "ext-mbstring": "*", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", - "symfony/finder": "^5.4 || ^6.0 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", - "symfony/process": "^5.4 || ^6.0 || ^7.0", - "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + "symfony/cache": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/finder": "^6.4 || ^7.0", + "symfony/options-resolver": "^6.4 || ^7.0", + "symfony/process": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", From b47dc0f0b2c94542b70d3b33d604f2cc3b54f8c3 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 06:32:45 +0000 Subject: [PATCH 06/53] clean-up code --- composer.json | 1 + src/Cache.php | 4 ---- src/Command/LintCommand.php | 2 +- src/Configuration/OptionsFactory.php | 3 +-- src/Output/JunitOutput.php | 4 ++++ 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 77e5fec6..fcc2ae46 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ ], "require": { "php": "^8.1", + "ext-dom": "*", "ext-json": "*", "ext-mbstring": "*", "symfony/cache": "^6.4 || ^7.0", diff --git a/src/Cache.php b/src/Cache.php index 4355d11b..75518eef 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -16,7 +16,6 @@ use LogicException; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\TraceableAdapter; @@ -43,9 +42,6 @@ final class Cache private int $hits = 0; private int $misses = 0; - private AdapterInterface $cache; - private LoggerInterface $logger; - public function __construct(string|object $cachePoolAdapter = null) { if (null === $cachePoolAdapter) { diff --git a/src/Command/LintCommand.php b/src/Command/LintCommand.php index 1bb62255..ab596f2b 100644 --- a/src/Command/LintCommand.php +++ b/src/Command/LintCommand.php @@ -74,7 +74,7 @@ protected function initialize(InputInterface $input, OutputInterface $output): v } /** - * @throws InvalidStyleException|Throwable + * @throws Throwable */ protected function execute(InputInterface $input, OutputInterface $output): int { diff --git a/src/Configuration/OptionsFactory.php b/src/Configuration/OptionsFactory.php index e406c90e..961e43e0 100644 --- a/src/Configuration/OptionsFactory.php +++ b/src/Configuration/OptionsFactory.php @@ -99,7 +99,7 @@ protected function configureOptions(OptionsResolver $resolver): void /** * Reused by unit tests suite ConsoleConfigTest */ - public static function logNormalizer(SymfonyOptions $options, $value) + public static function logNormalizer(SymfonyOptions $options, $value): bool|string { $bool = static::toBool($value); if (is_bool($bool)) { @@ -111,7 +111,6 @@ public static function logNormalizer(SymfonyOptions $options, $value) /** * Best strategy to convert string to boolean * - * * @link https://stackoverflow.com/questions/7336861/how-to-convert-string-to-boolean-php#answer-15075609 * @link https://www.php.net/manual/en/function.filter-var */ diff --git a/src/Output/JunitOutput.php b/src/Output/JunitOutput.php index 7b2c6c6e..c350b115 100644 --- a/src/Output/JunitOutput.php +++ b/src/Output/JunitOutput.php @@ -16,6 +16,7 @@ use DateTime; use DOMDocument; use DOMElement; +use DOMException; use Symfony\Component\Console\Output\StreamOutput; use function count; @@ -27,6 +28,9 @@ */ final class JunitOutput extends StreamOutput implements OutputInterface { + /** + * @throws DOMException + */ public function format(LinterOutput $results): void { $failures = $results->getFailures(); From b614660ad9f9bb9bca9643afd2198a3216cc5d23 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 07:50:53 +0000 Subject: [PATCH 07/53] clean-up code of examples --- examples/no-source-to-lint.php | 2 +- examples/no-yaml-configuration.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/no-source-to-lint.php b/examples/no-source-to-lint.php index de929439..71567b72 100644 --- a/examples/no-source-to-lint.php +++ b/examples/no-source-to-lint.php @@ -30,7 +30,7 @@ $input = new ArrayInput($arguments, $command->getDefinition()); $configResolver = new ConsoleOptionsResolver($input); -$finder = (new Finder($configResolver)); //->getFiles(); +$finder = new Finder($configResolver); $linter = new Linter($configResolver, $dispatcher); $results = $linter->lintFiles($finder->getFiles()); diff --git a/examples/no-yaml-configuration.php b/examples/no-yaml-configuration.php index 962a9f75..8bc8254b 100644 --- a/examples/no-yaml-configuration.php +++ b/examples/no-yaml-configuration.php @@ -34,9 +34,9 @@ $configResolver = new ConsoleOptionsResolver($input); -$finder = (new Finder($configResolver))->getFiles(); +$finder = new Finder($configResolver); $linter = new Linter($configResolver, $dispatcher); -$results = $linter->lintFiles($finder); +$results = $linter->lintFiles($finder->getFiles()); var_dump("Files checked :", count($results)); From d8d7246969df72a62c2cfc3553923c534b809f32 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 08:06:17 +0000 Subject: [PATCH 08/53] clean-up more code --- src/Linter.php | 3 +-- src/Process/LintProcess.php | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Linter.php b/src/Linter.php index d997744f..2198ef7e 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -64,8 +64,7 @@ public function __construct(Resolver $configResolver, EventDispatcherInterface $ } else { $adapter = new FilesystemAdapter('paths', 0, $configResolver->getOption(OptionDefinition::CACHE)); } - //$logger = new Logger(); - $this->cache = new Cache($adapter); //, $logger); + $this->cache = new Cache($adapter); $this->results = [ 'errors' => [], diff --git a/src/Process/LintProcess.php b/src/Process/LintProcess.php index 336fe07c..0f7d3569 100644 --- a/src/Process/LintProcess.php +++ b/src/Process/LintProcess.php @@ -64,31 +64,35 @@ public function getItem(string $output): LintProcessItem $out = explode("\n", trim($output)); $text = array_shift($out); + $match = []; + $message = ''; + if ($hasError) { $pattern = '/^(PHP\s+)?(Parse|Fatal) error:\s*(?:\w+ error,\s*)?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; $matched = preg_match($pattern, $text, $match); - if (empty($message)) { + if (empty($matched)) { $message = 'Unknown'; } } else { - $message = ''; $matched = false; } $errorString = $matched ? "{$match['error']} in line {$match['line']}" : $message; $errorLine = $matched ? (int) $match['line'] : 0; + $match = []; + $message = ''; + if ($hasWarning) { $pattern = '/^(PHP\s+)?(Warning|Deprecated|Notice):\s*?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; $matched = preg_match($pattern, $text, $match); - if (empty($message)) { + if (empty($matched)) { $message = 'Unknown'; } } else { - $message = ''; $matched = false; } $warningString = $matched ? "{$match['error']} in line {$match['line']}" : $message; From ffe15ae932b855812fd846be66ad9c1d1ddcfafc Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 08:45:05 +0000 Subject: [PATCH 09/53] removed prefix argument of Overtrue\PHPLint\Cache::clear method and follows psr/cache specification --- .changes/unreleased/Changed-20231207-084330.yaml | 4 ++++ src/Cache.php | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Changed-20231207-084330.yaml diff --git a/.changes/unreleased/Changed-20231207-084330.yaml b/.changes/unreleased/Changed-20231207-084330.yaml new file mode 100644 index 00000000..30cbc395 --- /dev/null +++ b/.changes/unreleased/Changed-20231207-084330.yaml @@ -0,0 +1,4 @@ +kind: Changed +body: Overtrue\PHPLint\Cache::clear follows Psr\Cache\CacheItemPoolInterface::clear + specification +time: 2023-12-07T08:43:30.059264515Z diff --git a/src/Cache.php b/src/Cache.php index 75518eef..e0d9f4b7 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -93,9 +93,9 @@ public function saveItem(CacheItemInterface $item): bool return $this->pool->save($item); } - public function clear(string $prefix = ''): bool + public function clear(): bool { - return $this->pool->clear($prefix); + return $this->pool->clear(); } public function isHit(string $filename): bool From 08a0f2440148b211a9dda4a6f0539698cb9913bd Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 09:06:55 +0000 Subject: [PATCH 10/53] Revert back on commit 164137fb79f8ce6ef8674c37b9f42fba7f3d8e5d, forgot that we can use Symfony\Component\Cache\Adapter\TraceableAdapter::clear() --- .changes/unreleased/Changed-20231207-084330.yaml | 4 ---- src/Cache.php | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 .changes/unreleased/Changed-20231207-084330.yaml diff --git a/.changes/unreleased/Changed-20231207-084330.yaml b/.changes/unreleased/Changed-20231207-084330.yaml deleted file mode 100644 index 30cbc395..00000000 --- a/.changes/unreleased/Changed-20231207-084330.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Changed -body: Overtrue\PHPLint\Cache::clear follows Psr\Cache\CacheItemPoolInterface::clear - specification -time: 2023-12-07T08:43:30.059264515Z diff --git a/src/Cache.php b/src/Cache.php index e0d9f4b7..b8661895 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -93,8 +93,11 @@ public function saveItem(CacheItemInterface $item): bool return $this->pool->save($item); } - public function clear(): bool + public function clear(string $prefix = ''): bool { + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix); + } return $this->pool->clear(); } From 2a1f6c5c82151db752ef3f0d197d39c38e86a600 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 09:33:09 +0000 Subject: [PATCH 11/53] console extensions to print progress must now implement a new interface --- .../unreleased/Changed-20231207-092611.yaml | 4 ++ src/Extension/ProgressBar.php | 21 ++++++- src/Extension/ProgressPrinter.php | 21 ++++++- src/Output/ConsoleOutput.php | 4 +- src/Output/ConsoleOutputInterface.php | 57 +++++++++++++++++++ 5 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 .changes/unreleased/Changed-20231207-092611.yaml create mode 100644 src/Output/ConsoleOutputInterface.php diff --git a/.changes/unreleased/Changed-20231207-092611.yaml b/.changes/unreleased/Changed-20231207-092611.yaml new file mode 100644 index 00000000..c56d6f17 --- /dev/null +++ b/.changes/unreleased/Changed-20231207-092611.yaml @@ -0,0 +1,4 @@ +kind: Changed +body: ProgressPrinter and ProgressBar extensions must now implement the Overtrue\PHPLint\Output\ConsoleOutputInterface + specification +time: 2023-12-07T09:26:11.396636982Z diff --git a/src/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index 23315474..1c136c36 100644 --- a/src/Extension/ProgressBar.php +++ b/src/Extension/ProgressBar.php @@ -13,6 +13,7 @@ namespace Overtrue\PHPLint\Extension; +use LogicException; use Overtrue\PHPLint\Event\AfterCheckingEvent; use Overtrue\PHPLint\Event\AfterCheckingInterface; use Overtrue\PHPLint\Event\AfterLintFileEvent; @@ -21,13 +22,15 @@ use Overtrue\PHPLint\Event\BeforeCheckingInterface; use Overtrue\PHPLint\Event\BeforeLintFileEvent; use Overtrue\PHPLint\Event\BeforeLintFileInterface; +use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use function get_class; use function mb_strimwidth; use function min; +use function sprintf; use function strlen; /** @@ -41,7 +44,7 @@ final class ProgressBar implements BeforeLintFileInterface, AfterLintFileInterface { - private OutputInterface $output; + private ConsoleOutputInterface $output; public static function getSubscribedEvents(): array { @@ -55,7 +58,19 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { - $this->output = $event->getOutput(); + $output = $event->getOutput(); + + if (!$output instanceof ConsoleOutputInterface) { + throw new LogicException( + sprintf( + 'Extension %s must implement %s', + get_class($this), + ConsoleOutputInterface::class + ) + ); + } + + $this->output = $output; } public function beforeChecking(BeforeCheckingEvent $event): void diff --git a/src/Extension/ProgressPrinter.php b/src/Extension/ProgressPrinter.php index 477aa3d7..1f5e405c 100644 --- a/src/Extension/ProgressPrinter.php +++ b/src/Extension/ProgressPrinter.php @@ -13,14 +13,17 @@ namespace Overtrue\PHPLint\Extension; +use LogicException; use Overtrue\PHPLint\Event\AfterLintFileEvent; use Overtrue\PHPLint\Event\AfterLintFileInterface; use Overtrue\PHPLint\Event\BeforeCheckingEvent; use Overtrue\PHPLint\Event\BeforeCheckingInterface; +use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use function get_class; +use function sprintf; /** * @author Laurent Laville @@ -31,7 +34,7 @@ final class ProgressPrinter implements BeforeCheckingInterface, AfterLintFileInterface { - private OutputInterface $output; + private ConsoleOutputInterface $output; private int $maxSteps = 0; @@ -47,7 +50,19 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { - $this->output = $event->getOutput(); + $output = $event->getOutput(); + + if (!$output instanceof ConsoleOutputInterface) { + throw new LogicException( + sprintf( + 'Extension %s must implement %s', + get_class($this), + ConsoleOutputInterface::class + ) + ); + } + + $this->output = $output; } public function beforeChecking(BeforeCheckingEvent $event): void diff --git a/src/Output/ConsoleOutput.php b/src/Output/ConsoleOutput.php index ae9b69f0..2df388dd 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/ConsoleOutput.php @@ -58,12 +58,10 @@ * @author Laurent Laville * @since Release 9.0.0 */ -class ConsoleOutput extends BaseConsoleOutput implements OutputInterface +class ConsoleOutput extends BaseConsoleOutput implements ConsoleOutputInterface { public const MAX_LINE_LENGTH = 120; - public const NO_FILE_TO_LINT = 'Could not find any files to lint'; - private ?ProgressBar $progressBar = null; private int $lineLength; diff --git a/src/Output/ConsoleOutputInterface.php b/src/Output/ConsoleOutputInterface.php new file mode 100644 index 00000000..fd11374b --- /dev/null +++ b/src/Output/ConsoleOutputInterface.php @@ -0,0 +1,57 @@ + Date: Thu, 7 Dec 2023 09:35:36 +0000 Subject: [PATCH 12/53] forgot to push CS fixes automagically applied --- src/Extension/ProgressBar.php | 2 +- src/Extension/ProgressPrinter.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index 1c136c36..8de77c76 100644 --- a/src/Extension/ProgressBar.php +++ b/src/Extension/ProgressBar.php @@ -64,7 +64,7 @@ public function initProgress(ConsoleCommandEvent $event): void throw new LogicException( sprintf( 'Extension %s must implement %s', - get_class($this), + get_class($this), ConsoleOutputInterface::class ) ); diff --git a/src/Extension/ProgressPrinter.php b/src/Extension/ProgressPrinter.php index 1f5e405c..f56ca6f1 100644 --- a/src/Extension/ProgressPrinter.php +++ b/src/Extension/ProgressPrinter.php @@ -22,6 +22,7 @@ use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use function get_class; use function sprintf; From 892e1f70f77d4ee65d6297ecc18814714bdc9e83 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 10:52:54 +0000 Subject: [PATCH 13/53] Cache::getCalls() may be used only with TraceableAdapter of Symfony Cache --- src/Cache.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Cache.php b/src/Cache.php index b8661895..6799ac1b 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -124,7 +124,10 @@ public function isHit(string $filename): bool public function getCalls(): array { - return $this->pool->getCalls(); + if ($this->pool instanceof TraceableAdapter) { + return $this->pool->getCalls(); + } + return []; } public function __debugInfo(): ?array From 0b0c6824c2c080ccfb419c38e90ea2f0d21dd19c Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 7 Dec 2023 11:22:13 +0000 Subject: [PATCH 14/53] comply with PHPStan level 5 --- src/Cache.php | 4 ++-- src/Finder.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Cache.php b/src/Cache.php index 6799ac1b..ca91cb3e 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -59,12 +59,12 @@ public function __construct(string|object $cachePoolAdapter = null) $adapter = $cachePoolAdapter; } - if (!$adapter instanceof CacheItemPoolInterface) { + if (!$adapter instanceof AdapterInterface) { throw new LogicException( sprintf( 'Invalid cache pool adapter. "%s" must implement %s.', $cachePoolAdapter, - CacheItemPoolInterface::class + AdapterInterface::class ) ); } diff --git a/src/Finder.php b/src/Finder.php index b290c4a1..3738b273 100644 --- a/src/Finder.php +++ b/src/Finder.php @@ -62,6 +62,7 @@ public function getFiles(): SymfonyFinder $finder->append($this->getFilesFromDir($path)); } elseif (is_file($path)) { $iterator = new ArrayIterator(); + // @phpstan-ignore-next-line $iterator[$path] = new SplFileInfo($path, $path, $path); $finder->append($iterator); } From 23d70ce133456e69f9eb5f1b3fafe04887680625 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Fri, 8 Dec 2023 06:17:19 +0000 Subject: [PATCH 15/53] Reorganized dev tools under their own composer namespace --- .changes/unreleased/Changed-20231208-061436.yaml | 4 ++++ composer.json | 10 +++++----- docs/contributing.md | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 .changes/unreleased/Changed-20231208-061436.yaml diff --git a/.changes/unreleased/Changed-20231208-061436.yaml b/.changes/unreleased/Changed-20231208-061436.yaml new file mode 100644 index 00000000..13dfd1a4 --- /dev/null +++ b/.changes/unreleased/Changed-20231208-061436.yaml @@ -0,0 +1,4 @@ +kind: Changed +body: Reorganize dev tools under their own composer namespace (check-style begins + style:check, and fix-style begins style:fix) +time: 2023-12-08T06:14:36.107069306Z diff --git a/composer.json b/composer.json index fcc2ae46..a79b417c 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "extra": { "hooks": { "pre-commit": [ - "composer fix-style" + "composer style:fix" ] }, "branch-alias": { @@ -72,8 +72,8 @@ "@composer bin all install --ansi" ], "cghooks": "vendor/bin/cghooks", - "check-style": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi --diff --dry-run", - "fix-style": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi", + "style:check": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi --diff --dry-run", + "style:fix": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi", "tests:unit": "vendor/bin/phpunit --testsuite=cache,configuration,finder", "tests:e2e": "vendor/bin/phpunit --testsuite=e2e", "tests:all": "vendor/bin/phpunit", @@ -82,8 +82,8 @@ "minimum-stability": "dev", "prefer-stable": true, "scripts-descriptions": { - "check-style": "Run style checks (only dry run - no fixing!).", - "fix-style": "Run style checks and fix violations." + "style:check": "Run style checks (only dry run - no fixing!).", + "style:fix": "Run style checks and fix violations." }, "bin": [ "bin/phplint" diff --git a/docs/contributing.md b/docs/contributing.md index af51eb2f..aa60b0f5 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -35,16 +35,16 @@ All dev tools (`php-cs-fixer`, `phpunit`) are under control of [bamarni/composer Coding standards are enforced using [PHP-CS-Fixer][php-cs-fixer] This project comes with a configuration file (located at `/.php-cs-fixer.dist.php` in the repository) -and an executable for PHP CS Fixer (located at `/tools/php-cs-fixer`) that you can use to (re)format your source code for compliance with this project's coding guidelines: +and an executable for PHP CS Fixer (located at `vendor/bin/php-cs-fixer`) that you can use to (re)format your source code for compliance with this project's coding guidelines: ```shell -composer fix-style +composer style:fix ``` If you only want to check source code standard violation, without apply changes, please use instead: ```shell -composer check-style +composer style:check ``` Here is a preview of what call look like: From 229aa08ee3ab2c8557bd710fbd3bf831fa9ee0af Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Fri, 8 Dec 2023 06:24:00 +0000 Subject: [PATCH 16/53] add composer scripts description about tests namespace --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a79b417c..da4fd759 100644 --- a/composer.json +++ b/composer.json @@ -83,7 +83,10 @@ "prefer-stable": true, "scripts-descriptions": { "style:check": "Run style checks (only dry run - no fixing!).", - "style:fix": "Run style checks and fix violations." + "style:fix": "Run style checks and fix violations.", + "tests:unit": "Run unit tests on following components: cache, configuration, finder", + "tests:e2e": "Run end to end tests", + "tests:all": "Run unit and end to end tests" }, "bin": [ "bin/phplint" From 106311349f3ff56fca7ce109f967639be7d4ad62 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Fri, 8 Dec 2023 06:26:35 +0000 Subject: [PATCH 17/53] add composer script description for lint:syntax --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index da4fd759..b26923e5 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,8 @@ "style:fix": "Run style checks and fix violations.", "tests:unit": "Run unit tests on following components: cache, configuration, finder", "tests:e2e": "Run end to end tests", - "tests:all": "Run unit and end to end tests" + "tests:all": "Run unit and end to end tests", + "lint:syntax": "Run PHPLint on it own source code" }, "bin": [ "bin/phplint" From 5d3cbed20cff1e5e5d160212132c236ecd835ff6 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Fri, 8 Dec 2023 06:43:39 +0000 Subject: [PATCH 18/53] added PHPStan dev tool to enforce code quality of this project --- .../unreleased/Added-20231208-064223.yaml | 3 +++ composer.json | 9 +++++--- docs/assets/phpstan_run.png | Bin 0 -> 14713 bytes docs/contributing.md | 20 ++++++++++++++++-- phpstan.neon.dist | 4 ++++ vendor-bin/phpstan/composer.json | 5 +++++ 6 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 .changes/unreleased/Added-20231208-064223.yaml create mode 100644 docs/assets/phpstan_run.png create mode 100644 phpstan.neon.dist create mode 100644 vendor-bin/phpstan/composer.json diff --git a/.changes/unreleased/Added-20231208-064223.yaml b/.changes/unreleased/Added-20231208-064223.yaml new file mode 100644 index 00000000..ffc8cfc5 --- /dev/null +++ b/.changes/unreleased/Added-20231208-064223.yaml @@ -0,0 +1,3 @@ +kind: Added +body: PHPStan dev tool to enforce code quality of this project (see Contributor guide) +time: 2023-12-08T06:42:23.834383742Z diff --git a/composer.json b/composer.json index b26923e5..5cad3c78 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,8 @@ "extra": { "hooks": { "pre-commit": [ - "composer style:fix" + "composer style:fix", + "composer code:check" ] }, "branch-alias": { @@ -77,7 +78,8 @@ "tests:unit": "vendor/bin/phpunit --testsuite=cache,configuration,finder", "tests:e2e": "vendor/bin/phpunit --testsuite=e2e", "tests:all": "vendor/bin/phpunit", - "lint:syntax": "./bin/phplint --ansi" + "lint:syntax": "./bin/phplint --ansi", + "code:check": "vendor/bin/phpstan analyse --configuration phpstan.neon.dist" }, "minimum-stability": "dev", "prefer-stable": true, @@ -87,7 +89,8 @@ "tests:unit": "Run unit tests on following components: cache, configuration, finder", "tests:e2e": "Run end to end tests", "tests:all": "Run unit and end to end tests", - "lint:syntax": "Run PHPLint on it own source code" + "lint:syntax": "Run PHPLint on it own source code", + "code:check": "Run PHPStan code analysis on PHPLint source code" }, "bin": [ "bin/phplint" diff --git a/docs/assets/phpstan_run.png b/docs/assets/phpstan_run.png new file mode 100644 index 0000000000000000000000000000000000000000..593b9fbd8525566710a4709b1329fd94fdf176f7 GIT binary patch literal 14713 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 Date: Sat, 9 Dec 2023 15:36:38 +0000 Subject: [PATCH 19/53] implement feature request #197 --- .../unreleased/Changed-20231209-153537.yaml | 3 + src/Linter.php | 79 ++++++++++++------- src/Output/ConsoleOutput.php | 15 ++-- src/Output/ConsoleOutputInterface.php | 4 +- src/Output/LinterOutput.php | 3 +- src/Process/LintProcess.php | 79 +++++++++++++------ src/Process/LintProcessItem.php | 9 +++ 7 files changed, 129 insertions(+), 63 deletions(-) create mode 100644 .changes/unreleased/Changed-20231209-153537.yaml diff --git a/.changes/unreleased/Changed-20231209-153537.yaml b/.changes/unreleased/Changed-20231209-153537.yaml new file mode 100644 index 00000000..7ccff574 --- /dev/null +++ b/.changes/unreleased/Changed-20231209-153537.yaml @@ -0,0 +1,3 @@ +kind: Changed +body: '[#197](https://github.com/overtrue/phplint/issues/197) : Faster process linter' +time: 2023-12-09T15:35:37.334855942Z diff --git a/src/Linter.php b/src/Linter.php index 2198ef7e..ca7a20b3 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -13,6 +13,7 @@ namespace Overtrue\PHPLint; +use Fiber; use LogicException; use Overtrue\PHPLint\Configuration\OptionDefinition; use Overtrue\PHPLint\Configuration\Resolver; @@ -27,11 +28,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; +use Throwable; +use function array_push; +use function array_slice; use function count; use function md5_file; use function microtime; -use function trim; +use function phpversion; +use function version_compare; /** * @author Overtrue @@ -74,6 +79,9 @@ public function __construct(Resolver $configResolver, EventDispatcherInterface $ ]; } + /** + * @throws Throwable + */ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutput { if (null === $startTime) { @@ -93,46 +101,58 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu ) ); + $processCount = 0; + if ($fileCount > 0) { - $pid = 0; - $processRunning = []; $iterator = $finder->getIterator(); - while ($iterator->valid() || !empty($processRunning)) { - for ($i = count($processRunning); $iterator->valid() && $i < $this->processLimit; ++$i) { + while ($iterator->valid()) { + for ($i = 0; $iterator->valid() && $i < $this->processLimit; ++$i) { $fileInfo = $iterator->current(); $this->dispatcher->dispatch(new BeforeLintFileEvent($this, ['file' => $fileInfo])); $filename = $fileInfo->getRealPath(); if ($this->cache->isHit($filename)) { - $this->results['hits'][] = $filename; + $this->results['hits'][] = $fileInfo; } else { - $lintProcess = $this->createLintProcess($filename); - $lintProcess->start(); - - ++$pid; - $processRunning[$pid] = [ - 'process' => $lintProcess, - 'file' => $fileInfo, - ]; - $this->results['misses'][] = $filename; + $this->results['misses'][] = $fileInfo; } $iterator->next(); } - foreach ($processRunning as $pid => $item) { - /** @var LintProcess $lintProcess */ - $lintProcess = $item['process']; + if (version_compare(phpversion(), '8.3', 'ge')) { + $offset = -1 * $i; + } else { + $offset = -1; + } + $files = array_slice($this->results['misses'], $offset, null, false); + + $fiber = new Fiber(function (array $files): void { + $lintProcess = $this->createLintProcess($files); + $lintProcess->start(); + Fiber::suspend($lintProcess); + }); + + $lintProcess = $fiber->start($files); + ++$processCount; + + while (!$fiber->isTerminated()) { if ($lintProcess->isRunning()) { + // php lint process is still running in background, wait until it's finished continue; } - /** @var SplFileInfo $fileInfo */ - $fileInfo = $item['file']; - $status = $this->processFile($fileInfo, $lintProcess); - unset($processRunning[$pid]); - $this->dispatcher->dispatch(new AfterLintFileEvent($this, ['file' => $fileInfo, 'status' => $status])); + // checks status of all files linked at end of the php lint process + foreach ($files as $fileInfo) { + $status = $this->processFile($fileInfo, $lintProcess); + + $this->dispatcher->dispatch( + new AfterLintFileEvent($this, ['file' => $fileInfo, 'status' => $status]) + ); + } + + $fiber->resume(); } } @@ -141,7 +161,7 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu $results = []; } $finalResults = new LinterOutput($results, $finder); - $finalResults->setContext($this->configResolver, $startTime); + $finalResults->setContext($this->configResolver, $startTime, $processCount); $this->dispatcher->dispatch(new AfterCheckingEvent($this, ['results' => $finalResults])); @@ -152,8 +172,7 @@ private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): s { $filename = $fileInfo->getRealPath(); - $output = trim($lintProcess->getOutput()); - $item = $lintProcess->getItem($output); + $item = $lintProcess->getItem($fileInfo); if ($item->hasSyntaxError()) { $status = 'error'; @@ -170,7 +189,7 @@ private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): s if ($status !== 'ok') { $this->results[$status . 's'][$filename] = [ 'absolute_file' => $filename, - 'relative_file' => $fileInfo->getRelativePathname(), + 'relative_file' => $item->getFileInfo()->getRelativePathname(), 'error' => $item->getMessage(), 'line' => $item->getLine(), ]; @@ -179,7 +198,7 @@ private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): s return $status; } - private function createLintProcess(string $filename): LintProcess + private function createLintProcess(array $files): LintProcess { $command = [ PHP_SAPI == 'cli' ? PHP_BINARY : PHP_BINDIR . '/php', @@ -192,8 +211,8 @@ private function createLintProcess(string $filename): LintProcess } $command[] = '-l'; - $command[] = $filename; + array_push($command, ...$files); - return new LintProcess($command); + return (new LintProcess($command))->setFiles($files); } } diff --git a/src/Output/ConsoleOutput.php b/src/Output/ConsoleOutput.php index 2df388dd..9e91ffcc 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/ConsoleOutput.php @@ -108,7 +108,7 @@ public function format(LinterOutput $results): void return; } - $this->consumeBlock($context['time_usage'], $context['memory_usage'], $context['cache_usage']); + $this->consumeBlock($context['time_usage'], $context['memory_usage'], $context['cache_usage'], $context['process_count']); if ($errCount > 0) { $this->errorBlock($context['files_count'], $errCount); @@ -171,7 +171,7 @@ public function progressMessage(string $message, string $name = 'message'): void $this->progressBar?->setMessage($message, $name); } - public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInfo $fileInfo): void + public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInfo $fileInfo, int $step = 1): void { static $i = 1; @@ -216,7 +216,8 @@ public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInf $this->newLine(); } } - ++$i; + + $i += $step; } public function headerBlock(string $appVersion, string $configFile): void @@ -298,13 +299,15 @@ public function configBlock(array $options): void $this->newLine(); } - public function consumeBlock(string $timeUsage, string $memUsage, string $cacheUsage): void + public function consumeBlock(string $timeUsage, string $memUsage, string $cacheUsage, int $processCount): void { $message = sprintf( - 'Time: %s, Memory: %s, Cache: %s', + 'Time: %s, Memory: %s, Cache: %s, Process%s: %s', $timeUsage, $memUsage, - $cacheUsage + $cacheUsage, + $processCount > 1 ? 'es' : '', + $processCount ); $this->newLine(); $this->writeln($message); diff --git a/src/Output/ConsoleOutputInterface.php b/src/Output/ConsoleOutputInterface.php index fd11374b..9d2bacfe 100644 --- a/src/Output/ConsoleOutputInterface.php +++ b/src/Output/ConsoleOutputInterface.php @@ -39,13 +39,13 @@ public function progressFinish(): void; public function progressMessage(string $message, string $name = 'message'): void; - public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInfo $fileInfo): void; + public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInfo $fileInfo, int $step = 1): void; public function headerBlock(string $appVersion, string $configFile): void; public function configBlock(array $options): void; - public function consumeBlock(string $timeUsage, string $memUsage, string $cacheUsage): void; + public function consumeBlock(string $timeUsage, string $memUsage, string $cacheUsage, int $processCount): void; public function errorBlock(int $fileCount, int $errorCount): void; diff --git a/src/Output/LinterOutput.php b/src/Output/LinterOutput.php index c35c01b0..d6a92e15 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): void + public function setContext(Resolver $configResolver, float $startTime, int $processCount): void { $cacheHits = count($this->getHits()); $cacheMisses = count($this->getMisses()); @@ -82,6 +82,7 @@ public function setContext(Resolver $configResolver, float $startTime): void 'time_usage' => $timeUsage, 'memory_usage' => $memUsage, 'cache_usage' => $cacheUsage, + 'process_count' => $processCount, 'files_count' => $fileCount, 'options_used' => $configResolver->getOptions(), ]; diff --git a/src/Process/LintProcess.php b/src/Process/LintProcess.php index 0f7d3569..7289f65b 100644 --- a/src/Process/LintProcess.php +++ b/src/Process/LintProcess.php @@ -14,13 +14,15 @@ namespace Overtrue\PHPLint\Process; use Closure; +use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Process\Process; -use function array_shift; -use function explode; +use function array_filter; use function preg_match; +use function preg_split; use function str_contains; -use function trim; + +use const PREG_SPLIT_NO_EMPTY; /** * @author Laurent Laville @@ -28,6 +30,8 @@ */ final class LintProcess extends Process { + private array $files; + private static Closure $createLintProcessItem; public function __construct(array $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60) @@ -35,7 +39,7 @@ public function __construct(array $command, string $cwd = null, array $env = nul parent::__construct($command, $cwd, $env, $input, $timeout); self::$createLintProcessItem = Closure::bind( - static function (bool $hasError, string $errorString, int $errorLine, bool $hasWarning, string $warningString, int $warningLine) { + static function (bool $hasError, string $errorString, int $errorLine, bool $hasWarning, string $warningString, int $warningLine, SplFileInfo $fileInfo) { $item = new LintProcessItem(); $item->hasSyntaxError = $hasError; $item->hasSyntaxWarning = $hasWarning; @@ -49,6 +53,7 @@ static function (bool $hasError, string $errorString, int $errorLine, bool $hasW $item->message = ''; $item->line = 0; } + $item->fileInfo = $fileInfo; return $item; }, null, @@ -56,48 +61,74 @@ static function (bool $hasError, string $errorString, int $errorLine, bool $hasW ); } - public function getItem(string $output): LintProcessItem + public function setFiles(array $files): self + { + $this->files = $files; + return $this; + } + + public function getFiles(): array { - $hasError = !str_contains($output, 'No syntax errors detected'); - $hasWarning = (bool) preg_match('/(Warning:|Deprecated:|Notice:)/', $output); + return $this->files; + } + + public function getItem(SplFileInfo $fileInfo): LintProcessItem + { + $filename = $fileInfo->getRelativePathname(); + + $messages = preg_split('/\n/', $this->getOutput(), -1, PREG_SPLIT_NO_EMPTY); + + $filtered = array_filter($messages, function ($message) use ($filename) { + return str_contains($message, $filename); + }); - $out = explode("\n", trim($output)); - $text = array_shift($out); + $output = [$filename => ['hasError' => false, 'hasWarning' => false, 'message' => '']]; + + foreach ($filtered as $message) { + $hasError = false; + $hasWarning = (bool) preg_match('/(Warning:|Deprecated:|Notice:)/', $message); + if ($hasWarning) { + $output[$filename]['hasWarning'] = true; + $output[$filename]['message'] = $message; + } elseif (!$output[$filename]['hasError']) { + $hasError = !str_contains($message, 'No syntax errors detected'); + $output[$filename]['hasError'] = $hasError; + } + if ($hasError) { + $output[$filename]['message'] = $message; + // stop on first error message returned by the PHP linter + break; + } + } + + $message = $output[$filename]['message']; + $hasError = $output[$filename]['hasError']; + $hasWarning = $output[$filename]['hasWarning']; $match = []; - $message = ''; if ($hasError) { $pattern = '/^(PHP\s+)?(Parse|Fatal) error:\s*(?:\w+ error,\s*)?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; - $matched = preg_match($pattern, $text, $match); - - if (empty($matched)) { - $message = 'Unknown'; - } + $matched = preg_match($pattern, $message, $match); } else { $matched = false; } - $errorString = $matched ? "{$match['error']} in line {$match['line']}" : $message; + $errorString = $matched ? "{$match['error']} in line {$match['line']}" : ''; $errorLine = $matched ? (int) $match['line'] : 0; $match = []; - $message = ''; if ($hasWarning) { $pattern = '/^(PHP\s+)?(Warning|Deprecated|Notice):\s*?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; - $matched = preg_match($pattern, $text, $match); - - if (empty($matched)) { - $message = 'Unknown'; - } + $matched = preg_match($pattern, $message, $match); } else { $matched = false; } - $warningString = $matched ? "{$match['error']} in line {$match['line']}" : $message; + $warningString = $matched ? "{$match['error']} in line {$match['line']}" : ''; $warningLine = $matched ? (int) $match['line'] : 0; - return (self::$createLintProcessItem)($hasError, $errorString, $errorLine, $hasWarning, $warningString, $warningLine); + return (self::$createLintProcessItem)($hasError, $errorString, $errorLine, $hasWarning, $warningString, $warningLine, $fileInfo); } } diff --git a/src/Process/LintProcessItem.php b/src/Process/LintProcessItem.php index 918e0113..2871d52b 100644 --- a/src/Process/LintProcessItem.php +++ b/src/Process/LintProcessItem.php @@ -13,6 +13,8 @@ namespace Overtrue\PHPLint\Process; +use Symfony\Component\Finder\SplFileInfo; + /** * @author Laurent Laville * @since Release 9.0.0 @@ -23,6 +25,8 @@ final class LintProcessItem protected bool $hasSyntaxWarning = false; protected string $message; protected int $line; + protected SplFileInfo $fileInfo; + public function hasSyntaxError(): bool { return $this->hasSyntaxError; @@ -41,4 +45,9 @@ public function getLine(): int { return $this->line; } + + public function getFileInfo(): SplFileInfo + { + return $this->fileInfo; + } } From 0a82c5a67c7c66fa3c25deffb8e50d8e71a516ff Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Wed, 13 Dec 2023 09:26:06 +0000 Subject: [PATCH 20/53] add a DebugFormatterHelper for asynchronous process --- .../unreleased/Added-20231213-092438.yaml | 3 + src/Helper/DebugFormatterHelper.php | 103 ++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 .changes/unreleased/Added-20231213-092438.yaml create mode 100644 src/Helper/DebugFormatterHelper.php diff --git a/.changes/unreleased/Added-20231213-092438.yaml b/.changes/unreleased/Added-20231213-092438.yaml new file mode 100644 index 00000000..089241ff --- /dev/null +++ b/.changes/unreleased/Added-20231213-092438.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Introduces a DebugFormatterHelper for asynchronous process +time: 2023-12-13T09:24:38.33870516Z diff --git a/src/Helper/DebugFormatterHelper.php b/src/Helper/DebugFormatterHelper.php new file mode 100644 index 00000000..5a355edf --- /dev/null +++ b/src/Helper/DebugFormatterHelper.php @@ -0,0 +1,103 @@ +started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + */ + public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string + { + $messages = preg_split('/\n/', $buffer, -1, PREG_SPLIT_NO_EMPTY); + + if ($error) { + $prefixed = sprintf('%s %s ', $this->getBorder($id), $prefix); + + if (!isset($this->started[$id]['err'])) { + $this->started[$id]['err'] = true; + } + } else { + $prefixed = sprintf('%s %s ', $this->getBorder($id), $prefix); + + if (!isset($this->started[$id]['out'])) { + $this->started[$id]['out'] = true; + } + } + + array_walk($messages, function (&$item, $key, $prefix) { + $item = $prefix . $item . "\n"; + }, $prefixed); + + return implode("\n", $messages); + } + + /** + * Stops a formatting session. + */ + public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + private function getBorder(string $id): string + { + return sprintf(' ', self::COLORS[$this->started[$id]['border']]); + } +} From 179041ceef1b2756abfc3414f69deda226a16ee7 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Wed, 13 Dec 2023 11:53:15 +0000 Subject: [PATCH 21/53] add a ProcessHelper for asynchronous process --- .../unreleased/Added-20231213-092714.yaml | 3 + src/Helper/ProcessHelper.php | 117 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .changes/unreleased/Added-20231213-092714.yaml create mode 100644 src/Helper/ProcessHelper.php diff --git a/.changes/unreleased/Added-20231213-092714.yaml b/.changes/unreleased/Added-20231213-092714.yaml new file mode 100644 index 00000000..8e76a350 --- /dev/null +++ b/.changes/unreleased/Added-20231213-092714.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Introduces a ProcessHelper for asynchronous process +time: 2023-12-13T09:27:14.222002659Z diff --git a/src/Helper/ProcessHelper.php b/src/Helper/ProcessHelper.php new file mode 100644 index 00000000..39add929 --- /dev/null +++ b/src/Helper/ProcessHelper.php @@ -0,0 +1,117 @@ +getFormatter(); + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if ($formatter && $verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($formatter, $output, $process, $callback); + } + + $process->start($callback, $env); + + return $process; + } + + public function isTerminated( + OutputInterface $output, + Process $process, + int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE + ): bool { + $formatter = $this->getFormatter(); + + $ended = $process->isTerminated(); + + if ($ended && $verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() + ? 'Command ran successfully' : + sprintf('%s Command did not run successfully', $process->getExitCode()) + ; + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + return $ended; + } + + private function getFormatter(): ?DebugFormatterHelper + { + /** @var ?DebugFormatterHelper $formatter */ + $formatter = $this->getHelperSet()->has('debug_formatter') + ? $this->getHelperSet()->get('debug_formatter') + : null + ; + return $formatter; + } + + private function wrapCallback( + DebugFormatterHelper $formatter, + OutputInterface $output, + Process $process, + callable $callback = null + ): callable { + return function ($type, $buffer) use ($output, $process, $callback, $formatter) { + $output->write( + $formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type) + ); + + if (null !== $callback) { + $callback($type, $buffer); + } + }; + } + + private function escapeString(string $str): string + { + return str_replace('<', '\\<', $str); + } +} From 5d124f5281baa6dcba2adee5ba0c3aa1bf4a70d0 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Wed, 13 Dec 2023 11:53:43 +0000 Subject: [PATCH 22/53] second implementation to solve feature #197 with PHPBench tests --- .gitignore | 3 +- phpbench.json | 3 + src/Command/LintCommand.php | 3 +- src/Console/Application.php | 25 +-- src/Extension/ProgressBar.php | 7 - src/Extension/ProgressPrinter.php | 18 +- src/Linter.php | 158 +++++++++++------- src/Process/LintProcess.php | 49 +++++- .../Console/Command/LintCommandBench.php | 76 +++++++++ tests/Finder/FinderTest.php | 2 +- 10 files changed, 252 insertions(+), 92 deletions(-) create mode 100644 phpbench.json create mode 100644 tests/Benchmark/Console/Command/LintCommandBench.php diff --git a/.gitignore b/.gitignore index 666323ff..25535641 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock .phplint.cache/ .idea .php-cs-fixer.cache -/vendor-bin/**/vendor \ No newline at end of file +/vendor-bin/**/vendor +.phpbench/ \ No newline at end of file diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 00000000..d3bc05d2 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,3 @@ +{ + "runner.bootstrap": "vendor/autoload.php" +} diff --git a/src/Command/LintCommand.php b/src/Command/LintCommand.php index ab596f2b..43cdb7ac 100644 --- a/src/Command/LintCommand.php +++ b/src/Command/LintCommand.php @@ -20,7 +20,6 @@ use Overtrue\PHPLint\Finder; use Overtrue\PHPLint\Linter; use Overtrue\PHPLint\Output\LinterOutput; -use PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -89,7 +88,7 @@ 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()); + $linter = new Linter($configResolver, $this->dispatcher, $app->getLongVersion(), $this->getHelperSet(), $output); $this->results = $linter->lintFiles($finder, $startTime); $data = $this->results->getFailures(); diff --git a/src/Console/Application.php b/src/Console/Application.php index 036f7a5c..07ba9ae6 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -13,12 +13,15 @@ namespace Overtrue\PHPLint\Console; +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\Command; 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; @@ -47,17 +50,6 @@ public function run(InputInterface $input = null, OutputInterface $output = null return parent::run($input, $output); } - public function doRun(InputInterface $input, OutputInterface $output): int - { - if (true === $input->hasParameterOption(['--manifest'], true)) { - $phar = new Phar($_SERVER['argv'][0]); - $manifest = $phar->getMetadata(); - $output->writeln($manifest); - return Command::SUCCESS; - } - return parent::doRun($input, $output); - } - protected function configureIO(InputInterface $input, OutputInterface $output): void { if (Phar::running()) { @@ -79,6 +71,15 @@ protected function getDefaultCommands(): array return [new HelpCommand(), new ListCommand()]; } + protected function getDefaultHelperSet(): HelperSet + { + return new HelperSet([ + new FormatterHelper(), + new DebugFormatterHelper(), + new ProcessHelper(), + ]); + } + protected function getCommandName(InputInterface $input): ?string { $name = parent::getCommandName($input); diff --git a/src/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index 8de77c76..d2eb1742 100644 --- a/src/Extension/ProgressBar.php +++ b/src/Extension/ProgressBar.php @@ -75,13 +75,6 @@ public function initProgress(ConsoleCommandEvent $event): void public function beforeChecking(BeforeCheckingEvent $event): void { - $configFile = $event->getArgument('options')['no-configuration'] - ? '' - : $event->getArgument('options')['configuration'] - ; - - $this->output->headerBlock($event->getArgument('appVersion'), $configFile); - $this->output->configBlock($event->getArgument('options')); $this->output->progressStart($event->getArgument('fileCount')); } diff --git a/src/Extension/ProgressPrinter.php b/src/Extension/ProgressPrinter.php index f56ca6f1..ee0fa6e5 100644 --- a/src/Extension/ProgressPrinter.php +++ b/src/Extension/ProgressPrinter.php @@ -21,6 +21,7 @@ use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use function get_class; @@ -38,6 +39,7 @@ final class ProgressPrinter implements private ConsoleOutputInterface $output; private int $maxSteps = 0; + private bool $hasProcessHelper; public static function getSubscribedEvents(): array { @@ -51,6 +53,8 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { + $this->hasProcessHelper = $event->getCommand()->getHelperSet()->has('process'); + $output = $event->getOutput(); if (!$output instanceof ConsoleOutputInterface) { @@ -68,19 +72,17 @@ public function initProgress(ConsoleCommandEvent $event): void public function beforeChecking(BeforeCheckingEvent $event): void { - $configFile = $event->getArgument('options')['no-configuration'] - ? '' - : $event->getArgument('options')['configuration'] - ; - - $this->output->headerBlock($event->getArgument('appVersion'), $configFile); - $this->output->configBlock($event->getArgument('options')); - $this->maxSteps = $event->getArgument('fileCount'); } public function afterLintFile(AfterLintFileEvent $event): void { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->getVerbosity() == OutputInterface::VERBOSITY_VERY_VERBOSE) { + // ProgressPrinter extension make some noise that break output when ProcessHelper is active in verbose level 2 + return; + } + $this->output->progressPrinterAdvance( $this->maxSteps, $event->getArgument('status'), diff --git a/src/Linter.php b/src/Linter.php index ca7a20b3..263200bb 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -13,7 +13,6 @@ namespace Overtrue\PHPLint; -use Fiber; use LogicException; use Overtrue\PHPLint\Configuration\OptionDefinition; use Overtrue\PHPLint\Configuration\Resolver; @@ -21,17 +20,21 @@ use Overtrue\PHPLint\Event\AfterLintFileEvent; 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 Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\NullAdapter; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\SplFileInfo; use Throwable; +use function array_chunk; use function array_push; -use function array_slice; use function count; use function md5_file; use function microtime; @@ -47,6 +50,8 @@ final class Linter private Resolver $configResolver; private EventDispatcherInterface $dispatcher; private Cache $cache; + private ?HelperSet $helperSet; + private ?OutputInterface $output; private array $results; private int $processLimit; private string $memoryLimit; @@ -54,8 +59,13 @@ final class Linter private array $options; private string $appLongVersion; - public function __construct(Resolver $configResolver, EventDispatcherInterface $dispatcher, string $appVersion = '9.1.x-dev') - { + public function __construct( + Resolver $configResolver, + EventDispatcherInterface $dispatcher, + string $appVersion = '9.1.x-dev', + HelperSet $helperSet = null, + OutputInterface $output = null + ) { $this->configResolver = $configResolver; $this->dispatcher = $dispatcher; $this->appLongVersion = $appVersion; @@ -77,6 +87,9 @@ public function __construct(Resolver $configResolver, EventDispatcherInterface $ 'hits' => [], 'misses' => [], ]; + + $this->helperSet = $helperSet; + $this->output = $output; } /** @@ -94,6 +107,15 @@ 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, @@ -102,64 +124,12 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu ); $processCount = 0; - if ($fileCount > 0) { - $iterator = $finder->getIterator(); - - while ($iterator->valid()) { - for ($i = 0; $iterator->valid() && $i < $this->processLimit; ++$i) { - $fileInfo = $iterator->current(); - $this->dispatcher->dispatch(new BeforeLintFileEvent($this, ['file' => $fileInfo])); - $filename = $fileInfo->getRealPath(); - - if ($this->cache->isHit($filename)) { - $this->results['hits'][] = $fileInfo; - } else { - $this->results['misses'][] = $fileInfo; - } - - $iterator->next(); - } - - if (version_compare(phpversion(), '8.3', 'ge')) { - $offset = -1 * $i; - } else { - $offset = -1; - } - $files = array_slice($this->results['misses'], $offset, null, false); - - $fiber = new Fiber(function (array $files): void { - $lintProcess = $this->createLintProcess($files); - $lintProcess->start(); - Fiber::suspend($lintProcess); - }); - - $lintProcess = $fiber->start($files); - ++$processCount; - - while (!$fiber->isTerminated()) { - if ($lintProcess->isRunning()) { - // php lint process is still running in background, wait until it's finished - continue; - } - - // checks status of all files linked at end of the php lint process - foreach ($files as $fileInfo) { - $status = $this->processFile($fileInfo, $lintProcess); - - $this->dispatcher->dispatch( - new AfterLintFileEvent($this, ['file' => $fileInfo, 'status' => $status]) - ); - } - - $fiber->resume(); - } - } - - $results = $this->results; + $results = $this->doLint($finder, $processCount); } else { $results = []; } + $finalResults = new LinterOutput($results, $finder); $finalResults->setContext($this->configResolver, $startTime, $processCount); @@ -168,6 +138,78 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu return $finalResults; } + private function doLint(Finder $finder, int &$processCount): array + { + $iterator = $finder->getIterator(); + + while ($iterator->valid()) { + $fileInfo = $iterator->current(); + + if ($this->cache->isHit($fileInfo->getRealPath())) { + $this->results['hits'][] = $fileInfo; + } else { + $this->results['misses'][] = $fileInfo; + } + + $iterator->next(); + } + unset($iterator); + + if (version_compare(phpversion(), '8.3', 'ge')) { + $chunkSize = $this->processLimit; + } else { + $chunkSize = 1; + } + $chunks = array_chunk($this->results['misses'], $chunkSize); + $processRunning = []; + + /** @var ?ProcessHelper $helper */ + $helper = $this->helperSet?->has('process') ? $this->helperSet?->get('process') : null; // @phpstan-ignore-line + + foreach ($chunks as $loop => $files) { + $lintProcess = $this->createLintProcess($files) + ->setHelper($helper) + ->setOutput($this->output) + ; + $lintProcess->begin(); + + // enqueue lint process as much as authorized by --jobs option (number of paralleled jobs to run) + ++$processCount; + $processRunning[$processCount] = $lintProcess; + + while (count($processRunning) >= $this->processLimit || (!empty($processRunning) && $loop == count($chunks) - 1)) { + $this->checkProcessRunning($processRunning); + } + } + + return $this->results; + } + + /** + * @param array $processRunning + */ + private function checkProcessRunning(array &$processRunning): void + { + foreach ($processRunning as $pid => $lintProcess) { + if (!$lintProcess->isFinished()) { + // php lint process is still running in background, wait until it's finished + continue; + } + unset($processRunning[$pid]); + + // checks status of all files linked at end of the php lint process + foreach ($lintProcess->getFiles() as $fileInfo) { + $this->dispatcher->dispatch(new BeforeLintFileEvent($this, ['file' => $fileInfo])); + + $status = $this->processFile($fileInfo, $lintProcess); + + $this->dispatcher->dispatch( + new AfterLintFileEvent($this, ['file' => $fileInfo, 'status' => $status]) + ); + } + } + } + private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): string { $filename = $fileInfo->getRealPath(); diff --git a/src/Process/LintProcess.php b/src/Process/LintProcess.php index 7289f65b..28fe3c06 100644 --- a/src/Process/LintProcess.php +++ b/src/Process/LintProcess.php @@ -14,6 +14,9 @@ namespace Overtrue\PHPLint\Process; use Closure; +use Overtrue\PHPLint\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\HelperInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\Process\Process; @@ -31,12 +34,19 @@ final class LintProcess extends Process { private array $files; - + private ?HelperInterface $helper; + private ?OutputInterface $output; private static Closure $createLintProcessItem; - public function __construct(array $command, string $cwd = null, array $env = null, mixed $input = null, ?float $timeout = 60) - { + public function __construct( + array $command, + string $cwd = null, + array $env = null, + mixed $input = null, + ?float $timeout = 60 + ) { parent::__construct($command, $cwd, $env, $input, $timeout); + $this->helper = null; self::$createLintProcessItem = Closure::bind( static function (bool $hasError, string $errorString, int $errorLine, bool $hasWarning, string $warningString, int $warningLine, SplFileInfo $fileInfo) { @@ -61,6 +71,22 @@ static function (bool $hasError, string $errorString, int $errorLine, bool $hasW ); } + public function setHelper(?HelperInterface $helper): self + { + if ($helper instanceof ProcessHelper) { + $this->helper = $helper; + } else { + $this->helper = null; + } + return $this; + } + + public function setOutput(?OutputInterface $output): self + { + $this->output = $output; + return $this; + } + public function setFiles(array $files): self { $this->files = $files; @@ -131,4 +157,21 @@ public function getItem(SplFileInfo $fileInfo): LintProcessItem return (self::$createLintProcessItem)($hasError, $errorString, $errorLine, $hasWarning, $warningString, $warningLine, $fileInfo); } + + public function begin(callable $callback = null, array $env = []): void + { + if ($this->helper instanceof ProcessHelper) { + $this->helper->start($this->output, $this, $callback, $env); + return; + } + parent::start($callback, $env); + } + + public function isFinished(): bool + { + if ($this->helper instanceof ProcessHelper) { + return $this->helper->isTerminated($this->output, $this); + } + return parent::isTerminated(); + } } diff --git a/tests/Benchmark/Console/Command/LintCommandBench.php b/tests/Benchmark/Console/Command/LintCommandBench.php new file mode 100644 index 00000000..41a5135d --- /dev/null +++ b/tests/Benchmark/Console/Command/LintCommandBench.php @@ -0,0 +1,76 @@ +runCommand([]); + } + + public function benchJobs10(): void + { + $this->runCommand(['--jobs' => 10]); + } + + public function benchJobs100(): void + { + $this->runCommand(['--jobs' => 100]); + } + + public function benchJobs1000(): void + { + $this->runCommand(['--jobs' => 1000]); + } + + /** + * @throws Throwable + */ + private function runCommand(array $arguments): void + { + $arguments = \array_merge([ + 'path' => [dirname(__DIR__, 4) . '/vendor-bin/phpunit/vendor/phpunit/phpunit/src'], + '--no-cache' => true, + '--no-configuration' => true, + ], $arguments); + + $dispatcher = new EventDispatcher([]); + + $defaultCommand = new LintCommand($dispatcher); + + $application = new Application(); + $application->add($defaultCommand); + $application->setDefaultCommand($defaultCommand->getName()); + $application->setDispatcher($dispatcher); + + $commandTester = new CommandTester($defaultCommand); + $commandTester->execute($arguments); + } +} diff --git a/tests/Finder/FinderTest.php b/tests/Finder/FinderTest.php index 6fa8b702..c7b8dfd4 100644 --- a/tests/Finder/FinderTest.php +++ b/tests/Finder/FinderTest.php @@ -98,7 +98,7 @@ public function testSearchPhpFilesWithCondition(): void $arguments = [ OptionDefinition::PATH => [$basePath], '--no-configuration' => true, - '--' . OptionDefinition::EXCLUDE => ['fixtures'], + '--' . OptionDefinition::EXCLUDE => ['fixtures', 'Benchmark'], '--' . OptionDefinition::EXTENSIONS => ['php'] ]; $definition = (new LintCommand($dispatcher))->getDefinition(); From d1fb97b887d48ecd18f39a15d50a0c36eea72ad1 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Wed, 13 Dec 2023 15:02:19 +0000 Subject: [PATCH 23/53] fixed ProgressBar extension related to second implementation of #197 --- src/Extension/ProgressBar.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index d2eb1742..3ab6689d 100644 --- a/src/Extension/ProgressBar.php +++ b/src/Extension/ProgressBar.php @@ -25,6 +25,7 @@ use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use function get_class; @@ -45,6 +46,7 @@ final class ProgressBar implements AfterLintFileInterface { private ConsoleOutputInterface $output; + private bool $hasProcessHelper; public static function getSubscribedEvents(): array { @@ -58,6 +60,8 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { + $this->hasProcessHelper = $event->getCommand()->getHelperSet()->has('process'); + $output = $event->getOutput(); if (!$output instanceof ConsoleOutputInterface) { @@ -75,6 +79,11 @@ public function initProgress(ConsoleCommandEvent $event): void public function beforeChecking(BeforeCheckingEvent $event): void { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->getVerbosity() == OutputInterface::VERBOSITY_VERY_VERBOSE) { + // ProgressBar extension make some noise that break output when ProcessHelper is active in verbose level 2 + return; + } $this->output->progressStart($event->getArgument('fileCount')); } @@ -94,6 +103,12 @@ public function beforeLintFile(BeforeLintFileEvent $event): void public function afterLintFile(AfterLintFileEvent $event): void { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->getVerbosity() == OutputInterface::VERBOSITY_VERY_VERBOSE) { + // ProgressBar extension make some noise that break output when ProcessHelper is active in verbose level 2 + return; + } + $this->output->progressAdvance(); } } From b474ec95b8b9cd573b161872715922db2599aa0a Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 05:53:05 +0000 Subject: [PATCH 24/53] fix again ProgressBar extension on conflict between ProcessHelper and verbosity level 2 or 3 (#197) --- src/Extension/ProgressBar.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index 3ab6689d..553ee679 100644 --- a/src/Extension/ProgressBar.php +++ b/src/Extension/ProgressBar.php @@ -25,7 +25,6 @@ use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use function get_class; @@ -80,8 +79,8 @@ public function initProgress(ConsoleCommandEvent $event): void public function beforeChecking(BeforeCheckingEvent $event): void { // @phpstan-ignore-next-line - if ($this->hasProcessHelper && $this->output->getVerbosity() == OutputInterface::VERBOSITY_VERY_VERBOSE) { - // ProgressBar extension make some noise that break output when ProcessHelper is active in verbose level 2 + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + // ProgressBar extension make some noise that break output when ProcessHelper is active return; } $this->output->progressStart($event->getArgument('fileCount')); @@ -104,8 +103,8 @@ public function beforeLintFile(BeforeLintFileEvent $event): void public function afterLintFile(AfterLintFileEvent $event): void { // @phpstan-ignore-next-line - if ($this->hasProcessHelper && $this->output->getVerbosity() == OutputInterface::VERBOSITY_VERY_VERBOSE) { - // ProgressBar extension make some noise that break output when ProcessHelper is active in verbose level 2 + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + // ProgressBar extension make some noise that break output when ProcessHelper is active return; } From e1d669ce0847475a0ad98ca60e7ea6f05a12d585 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 05:58:57 +0000 Subject: [PATCH 25/53] add new ProgressIndicator extension --- .../unreleased/Added-20231214-055634.yaml | 3 + bin/phplint | 10 +- src/Extension/ProgressIndicator.php | 111 ++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 .changes/unreleased/Added-20231214-055634.yaml create mode 100644 src/Extension/ProgressIndicator.php diff --git a/.changes/unreleased/Added-20231214-055634.yaml b/.changes/unreleased/Added-20231214-055634.yaml new file mode 100644 index 00000000..6c8994f6 --- /dev/null +++ b/.changes/unreleased/Added-20231214-055634.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Introduces a new extension to show progression (ProgressIndicator). Uses --progress=indicator +time: 2023-12-14T05:56:34.231275002Z diff --git a/bin/phplint b/bin/phplint index b97cc9c8..5c8c778e 100755 --- a/bin/phplint +++ b/bin/phplint @@ -29,6 +29,7 @@ use Overtrue\PHPLint\Console\Application; use Overtrue\PHPLint\Event\EventDispatcher; use Overtrue\PHPLint\Extension\OutputFormat; use Overtrue\PHPLint\Extension\ProgressBar; +use Overtrue\PHPLint\Extension\ProgressIndicator; use Overtrue\PHPLint\Extension\ProgressPrinter; use Symfony\Component\Console\Input\ArgvInput; @@ -40,9 +41,12 @@ if (true === $input->hasParameterOption(['--no-progress'], true)) { $extensions = []; } elseif (true === $input->hasParameterOption(['--progress'], true)) { $progress = $input->getParameterOption('--progress', 'printer'); - if ('bar' === $progress) { - $extensions = [new ProgressBar()]; - } + + $extensions = match($progress) { + 'meter' => [new ProgressPrinter()], + 'bar' => [new ProgressBar()], + 'indicator' => [new ProgressIndicator()], + }; } $extensions[] = new OutputFormat([ diff --git a/src/Extension/ProgressIndicator.php b/src/Extension/ProgressIndicator.php new file mode 100644 index 00000000..8cd984fb --- /dev/null +++ b/src/Extension/ProgressIndicator.php @@ -0,0 +1,111 @@ + 'initProgress', + ]; + } + + /** + * Initialize progress indicator extension + */ + public function initProgress(ConsoleCommandEvent $event): void + { + $this->hasProcessHelper = $event->getCommand()->getHelperSet()->has('process'); + + $output = $event->getOutput(); + + if (!$output instanceof ConsoleOutputInterface) { + throw new LogicException( + sprintf( + 'Extension %s must implement %s', + get_class($this), + ConsoleOutputInterface::class + ) + ); + } + + $this->output = $output; + + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + $this->progressIndicator = null; + } else { + $this->progressIndicator = new ProgressIndicatorHelper( + $output, + 'normal', + 100, + ['⠏', '⠛', '⠹', '⢸', '⣰', '⣤', '⣆', '⡇'] + ); + } + } + + public function beforeChecking(BeforeCheckingEvent $event): void + { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + // ProgressIndicator extension make some noise that break output when ProcessHelper is active + return; + } + + $this->progressIndicator?->start('Linting files ...'); + } + + public function afterChecking(AfterCheckingEvent $event): void + { + $this->progressIndicator?->finish('Finished'); + } + + public function afterLintFile(AfterLintFileEvent $event): void + { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + // ProgressIndicator extension make some noise that break output when ProcessHelper is active + return; + } + + $this->progressIndicator?->advance(); + } +} From daa11b9ce2a2b42c2d5270111ead238a2b5c5210 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 06:38:34 +0000 Subject: [PATCH 26/53] update documentation about the new progress indicator extension --- README.md | 2 +- docs/architecture/extension.md | 20 ++++++++++++++++++++ docs/assets/progress-indicator-finished.png | Bin 0 -> 1753 bytes docs/assets/progress-indicator-running.png | Bin 0 -> 2211 bytes 4 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/assets/progress-indicator-finished.png create mode 100644 docs/assets/progress-indicator-running.png diff --git a/README.md b/README.md index 26b21907..4722b094 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Version 9.1 is based on 9.0 code but did not support anymore PHP 8.0 that reached End-Of-Life. Major version 9.0 is a full code rewrites that have the same source code (`main` branch) for all PHP 8.x versions (`4.x`, `5.x` or `6.x`), -with lot of improvement, full documentation, and full unit code tested. +with a lot of improvement, full documentation, and full unit code tested. ## Table of Contents diff --git a/docs/architecture/extension.md b/docs/architecture/extension.md index 2c87a609..123ede03 100644 --- a/docs/architecture/extension.md +++ b/docs/architecture/extension.md @@ -40,6 +40,15 @@ Here is preview of what it will look like : ![Progress Bar Verbose Max](../assets/progress-bar-verbose-max.png) +## ProgressIndicator + +This extension is useful to let users know that the `phplint` command isn't stalled. +Learn more with the official Symfony documentation on [ProgressIndicator Console Helper][symfony-progressindicator] + +![Progress Indicator Running](../assets/progress-indicator-running.png) + +![Progress Indicator Finished](../assets/progress-indicator-finished.png) + ## Example(s) Default progress printer widget: @@ -62,6 +71,16 @@ $extensions = [new ProgressBar()]; ``` +Default progress indicator widget: + +```php +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 literal 0 HcmV?d00001 From 3a8dbe785ad8cffbfeae5d9082da29f21de6dfaa Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 07:04:55 +0000 Subject: [PATCH 27/53] avoid to commit site folder (locally documentation generated) --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 25535641..1e07ad01 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ composer.lock .idea .php-cs-fixer.cache /vendor-bin/**/vendor -.phpbench/ \ No newline at end of file +.phpbench/ +site/ \ No newline at end of file From bddb3df00894bba3bdc8c6ab6a4a74a21c7efa5a Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 07:58:54 +0000 Subject: [PATCH 28/53] fixed DebugFormatterHelper --- src/Helper/DebugFormatterHelper.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Helper/DebugFormatterHelper.php b/src/Helper/DebugFormatterHelper.php index 5a355edf..a549bd31 100644 --- a/src/Helper/DebugFormatterHelper.php +++ b/src/Helper/DebugFormatterHelper.php @@ -16,6 +16,7 @@ use Symfony\Component\Console\Helper\Helper; use function array_walk; +use function count; use function implode; use function preg_split; @@ -45,7 +46,7 @@ public function getName(): string */ public function start(string $id, string $message, string $prefix = 'RUN'): string { - $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)]; + $this->started[$id] = ['border' => ++$this->count % count(self::COLORS)]; return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); } @@ -58,7 +59,7 @@ public function progress(string $id, string $buffer, bool $error = false, string $messages = preg_split('/\n/', $buffer, -1, PREG_SPLIT_NO_EMPTY); if ($error) { - $prefixed = sprintf('%s %s ', $this->getBorder($id), $prefix); + $prefixed = sprintf('%s %s ', $this->getBorder($id), $errorPrefix); if (!isset($this->started[$id]['err'])) { $this->started[$id]['err'] = true; From 31fb76e7ba2df54816905b1207543577bb4e85b4 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 09:07:42 +0000 Subject: [PATCH 29/53] update phpdoc blocks --- src/Cache.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Cache.php b/src/Cache.php index ca91cb3e..c538e04a 100644 --- a/src/Cache.php +++ b/src/Cache.php @@ -16,6 +16,7 @@ use LogicException; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Psr\Cache\InvalidArgumentException; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\TraceableAdapter; @@ -78,11 +79,17 @@ public function createCachePool(AdapterInterface $adapter): CacheItemPoolInterfa return new TraceableAdapter($adapter); } + /** + * @throws InvalidArgumentException + */ public function hasItem(string $filename): bool { return $this->pool->hasItem($this->getKey($filename)); } + /** + * @throws InvalidArgumentException + */ public function getItem(string $filename): CacheItemInterface { return $this->pool->getItem($this->getKey($filename)); @@ -101,6 +108,9 @@ public function clear(string $prefix = ''): bool return $this->pool->clear(); } + /** + * @throws InvalidArgumentException + */ public function isHit(string $filename): bool { // Try to fetch item from cache From 136166b79e8ff3402569f924207ad0119d68bc46 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 09:09:33 +0000 Subject: [PATCH 30/53] update syntax on catching exceptions only by type (see https://php.watch/versions/8.0/catch-exception-type) --- src/Configuration/FileOptionsResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Configuration/FileOptionsResolver.php b/src/Configuration/FileOptionsResolver.php index 8002a56f..d3410f61 100644 --- a/src/Configuration/FileOptionsResolver.php +++ b/src/Configuration/FileOptionsResolver.php @@ -33,7 +33,7 @@ public function __construct(InputInterface $input) try { $configuration = Yaml::parseFile($configFile); - } catch (ParseException $e) { + } catch (ParseException) { // If the file could not be read or the YAML is not valid $configuration = []; } From 17e0f8e314fad755902c14d98b8021dceaf917ae Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 10:00:00 +0000 Subject: [PATCH 31/53] avoid dupplicated header and config blocks to be displayed --- src/Output/ConsoleOutput.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Output/ConsoleOutput.php b/src/Output/ConsoleOutput.php index 9e91ffcc..4969d141 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/ConsoleOutput.php @@ -90,17 +90,6 @@ public function format(LinterOutput $results): void $data = $results->getFailures(); $context = $results->getContext(); - if (true === $this->configResolver->getOption("no-progress")) { - if (true === $this->configResolver->getOption("no-configuration")) { - $configFile = ''; - } else { - $configFile = $this->configResolver->getOption(OptionDefinition::CONFIGURATION); - } - - $this->headerBlock($this->appVersion, $configFile); - $this->configBlock($this->configResolver->getOptions()); - } - $errCount = count($data); if ($context['files_count'] === 0) { From 6e965bb523cd9320ab148b2a9456d714560887a0 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 10:03:38 +0000 Subject: [PATCH 32/53] upgrade ConsoleOutputInterface by removing two methods now unused --- .changes/unreleased/Removed-20231214-095810.yaml | 4 ++++ src/Extension/OutputFormat.php | 7 +------ src/Output/ConsoleOutput.php | 14 -------------- src/Output/ConsoleOutputInterface.php | 5 ----- 4 files changed, 5 insertions(+), 25 deletions(-) create mode 100644 .changes/unreleased/Removed-20231214-095810.yaml diff --git a/.changes/unreleased/Removed-20231214-095810.yaml b/.changes/unreleased/Removed-20231214-095810.yaml new file mode 100644 index 00000000..d96a0075 --- /dev/null +++ b/.changes/unreleased/Removed-20231214-095810.yaml @@ -0,0 +1,4 @@ +kind: Removed +body: '"setApplicationVersion" and "setConfigResolver" methods were removed from "Overtrue\PHPLint\Output\ConsoleOutputInterface" + as there are no more required' +time: 2023-12-14T09:58:10.457061253Z diff --git a/src/Extension/OutputFormat.php b/src/Extension/OutputFormat.php index 07b810fd..5d145066 100644 --- a/src/Extension/OutputFormat.php +++ b/src/Extension/OutputFormat.php @@ -74,12 +74,7 @@ public function initFormat(ConsoleCommandEvent $event): void } } - /** @var ConsoleOutput $consoleOutput */ - $consoleOutput = $event->getOutput(); - $consoleOutput->setApplicationVersion($event->getCommand()->getApplication()->getLongVersion()); - $consoleOutput->setConfigResolver($configResolver); - - $this->handlers[] = $consoleOutput; + $this->handlers[] = $event->getOutput(); } public function afterChecking(AfterCheckingEvent $event): void diff --git a/src/Output/ConsoleOutput.php b/src/Output/ConsoleOutput.php index 4969d141..db6fe885 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/ConsoleOutput.php @@ -13,8 +13,6 @@ namespace Overtrue\PHPLint\Output; -use Overtrue\PHPLint\Configuration\OptionDefinition; -use Overtrue\PHPLint\Configuration\Resolver; use PHP_Parallel_Lint\PhpConsoleColor\ConsoleColor; use PHP_Parallel_Lint\PhpConsoleColor\InvalidStyleException; use PHP_Parallel_Lint\PhpConsoleHighlighter\Highlighter; @@ -65,8 +63,6 @@ class ConsoleOutput extends BaseConsoleOutput implements ConsoleOutputInterface private ?ProgressBar $progressBar = null; private int $lineLength; - private Resolver $configResolver; - private string $appVersion; public function __construct(int $verbosity = parent::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) { @@ -75,16 +71,6 @@ public function __construct(int $verbosity = parent::VERBOSITY_NORMAL, bool $dec $this->lineLength = min($width - (int) (DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); } - public function setApplicationVersion(string $version): void - { - $this->appVersion = $version; - } - - public function setConfigResolver(Resolver $resolver): void - { - $this->configResolver = $resolver; - } - public function format(LinterOutput $results): void { $data = $results->getFailures(); diff --git a/src/Output/ConsoleOutputInterface.php b/src/Output/ConsoleOutputInterface.php index 9d2bacfe..41d38b98 100644 --- a/src/Output/ConsoleOutputInterface.php +++ b/src/Output/ConsoleOutputInterface.php @@ -13,7 +13,6 @@ namespace Overtrue\PHPLint\Output; -use Overtrue\PHPLint\Configuration\Resolver; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Finder\SplFileInfo; @@ -25,10 +24,6 @@ interface ConsoleOutputInterface extends OutputInterface { public const NO_FILE_TO_LINT = 'Could not find any files to lint'; - public function setApplicationVersion(string $version): void; - - public function setConfigResolver(Resolver $resolver): void; - public function createProgressBar($max = 0): ProgressBar; public function progressStart(int $max = 0): void; From 7d81f1bb4f972a76d98cdceae60e4fb20ef89f91 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 11:44:55 +0000 Subject: [PATCH 33/53] disable code coverage on workflows to improve PHP performance --- .github/workflows/lint.yml | 1 + .github/workflows/release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index afed679b..990ce4da 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -34,6 +34,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: "${{ matrix.php-version }}" + coverage: "none" tools: phpunit:10.5 - # https://github.com/ramsey/composer-install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1bfb4bf8..feb6e909 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + coverage: "none" tools: ${{ matrix.tools }} - # https://github.com/ramsey/composer-install From 99306eaea9c8cc24b37de52e16cb8915740bcb35 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 12:08:21 +0000 Subject: [PATCH 34/53] CI: upgrade PHAR build process but keep a version still PHP 8.1 compatible for distribution --- .github/workflows/release.yml | 4 ++-- composer.json | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index feb6e909..35f9984f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,9 +20,9 @@ jobs: os: - ubuntu-22.04 php: - - 8.1 + - 8.2 tools: - - box:4.5 # available since https://github.com/shivammathur/setup-php/releases/tag/2.27.0 + - box:4.6 # available since https://github.com/shivammathur/setup-php/releases/tag/2.27.0 steps: - # https://github.com/actions/checkout diff --git a/composer.json b/composer.json index 5cad3c78..9378bd85 100644 --- a/composer.json +++ b/composer.json @@ -96,6 +96,9 @@ "bin/phplint" ], "config": { + "platform": { + "php": "8.1" + }, "sort-packages": true, "optimize-autoloader": true, "allow-plugins": { From 1d47e87c3c57ec91fc5f1c565f92ff45f0d3eed7 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Thu, 14 Dec 2023 14:48:58 +0000 Subject: [PATCH 35/53] update phpdoc blocks --- src/Linter.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Linter.php b/src/Linter.php index 263200bb..c5f82b51 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -24,6 +24,7 @@ use Overtrue\PHPLint\Output\ConsoleOutputInterface; use Overtrue\PHPLint\Output\LinterOutput; use Overtrue\PHPLint\Process\LintProcess; +use Psr\Cache\InvalidArgumentException; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\NullAdapter; use Symfony\Component\Console\Helper\HelperSet; @@ -138,6 +139,9 @@ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutpu return $finalResults; } + /** + * @throws InvalidArgumentException + */ private function doLint(Finder $finder, int &$processCount): array { $iterator = $finder->getIterator(); @@ -187,6 +191,7 @@ private function doLint(Finder $finder, int &$processCount): array /** * @param array $processRunning + * @throws InvalidArgumentException */ private function checkProcessRunning(array &$processRunning): void { @@ -210,6 +215,9 @@ private function checkProcessRunning(array &$processRunning): void } } + /** + * @throws InvalidArgumentException + */ private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): string { $filename = $fileInfo->getRealPath(); From e5765468a7a71c7ea55b2919edd8f189a7569412 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 06:07:57 +0000 Subject: [PATCH 36/53] update jetbrains/phpstorm-stubs requirement to v2023.3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9378bd85..76f58402 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", "brainmaestro/composer-git-hooks": "^2.8.5 || 3.0.0-alpha.1", - "jetbrains/phpstorm-stubs": "^2021.3 || ^2022.3 || ^2023.0", + "jetbrains/phpstorm-stubs": "^2021.3 || ^2022.3 || ^2023.3", "bamarni/composer-bin-plugin": "^1.4" }, "autoload": { From ea51861b5a918804665bcfb8830f424a0e34234f Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 06:32:12 +0000 Subject: [PATCH 37/53] revert partially on commit 5d94994647d123d30e684cfb2cf23b1c808a6032 to allows only CI to build a PHAR version PHP 8.1 compatible --- .github/workflows/release.yml | 5 +++++ composer.json | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35f9984f..ef587203 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,6 +37,11 @@ jobs: coverage: "none" tools: ${{ matrix.tools }} + - # https://getcomposer.org/doc/06-config.md#platform + name: Setup Composer Platform + run: | + composer config platform.php 8.1 + - # https://github.com/ramsey/composer-install name: Install Composer dependencies uses: ramsey/composer-install@v2 diff --git a/composer.json b/composer.json index 76f58402..1d6d4e41 100644 --- a/composer.json +++ b/composer.json @@ -96,9 +96,6 @@ "bin/phplint" ], "config": { - "platform": { - "php": "8.1" - }, "sort-packages": true, "optimize-autoloader": true, "allow-plugins": { From 4dde5513c92effc595efa09adba96bfdc14a4ac8 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 06:47:06 +0000 Subject: [PATCH 38/53] reduce PHAR size by 3.7 (from 2.39 Mb to 634 Kb) --- box.json | 1 + 1 file changed, 1 insertion(+) diff --git a/box.json b/box.json index 368bd6e8..60d7afc0 100644 --- a/box.json +++ b/box.json @@ -1,4 +1,5 @@ { + "compression": "GZ", "chmod": "0700", "banner": [ "This file is part of the overtrue/phplint package", From e0733abd7eadd62217afe27a27e1e74f487d1197 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 06:50:47 +0000 Subject: [PATCH 39/53] renamed BOX config file --- .changes/unreleased/Changed-20231216-064904.yaml | 3 +++ box.json => box.json.dist | 0 2 files changed, 3 insertions(+) create mode 100644 .changes/unreleased/Changed-20231216-064904.yaml rename box.json => box.json.dist (100%) diff --git a/.changes/unreleased/Changed-20231216-064904.yaml b/.changes/unreleased/Changed-20231216-064904.yaml new file mode 100644 index 00000000..a7d23e9e --- /dev/null +++ b/.changes/unreleased/Changed-20231216-064904.yaml @@ -0,0 +1,3 @@ +kind: Changed +body: rename BOX config file to box.json.dist +time: 2023-12-16T06:49:04.321497121Z diff --git a/box.json b/box.json.dist similarity index 100% rename from box.json rename to box.json.dist From d57404d356f68c09088bf67df7212c72f1367a35 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 06:56:30 +0000 Subject: [PATCH 40/53] Dockerfile bump default PHP version from 8.2 to 8.3 --- .changes/unreleased/Changed-20231216-065543.yaml | 3 +++ Dockerfile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Changed-20231216-065543.yaml diff --git a/.changes/unreleased/Changed-20231216-065543.yaml b/.changes/unreleased/Changed-20231216-065543.yaml new file mode 100644 index 00000000..981ffc16 --- /dev/null +++ b/.changes/unreleased/Changed-20231216-065543.yaml @@ -0,0 +1,3 @@ +kind: Changed +body: Dockerfile bump default PHP version from 8.2 to 8.3 (to produce better perf) +time: 2023-12-16T06:55:43.932148624Z diff --git a/Dockerfile b/Dockerfile index 7d746abe..fd0aeea3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.4 -ARG PHP_VERSION=8.2 +ARG PHP_VERSION=8.3 FROM php:${PHP_VERSION}-cli-alpine @@ -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.0 +RUN composer global require --no-progress overtrue/phplint ^9.1 # Following recommendation at https://docs.github.com/en/actions/creating-actions/dockerfile-support-for-github-actions#workdir From eaa6ca054e195417aed50d56350dfe020c0b701c Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sat, 16 Dec 2023 08:29:06 +0000 Subject: [PATCH 41/53] update Architecture doc --- docs/architecture/output.md | 35 +++++++++++++------------ docs/assets/console-output-example.png | Bin 31488 -> 57684 bytes 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/docs/architecture/output.md b/docs/architecture/output.md index 8fe5ba83..2ed34c80 100644 --- a/docs/architecture/output.md +++ b/docs/architecture/output.md @@ -35,39 +35,39 @@ This handler is responsible to print PHPLint results on JSON private format. For "relative_file": "fixtures/syntax_error.php", "error": "unexpected end of file in line 4", "line": 4 + }, + "/path/to/tests/fixtures/php-8.2_syntax.php": { + "absolute_file": "/path/to/tests/fixtures/php-8.2_syntax.php", + "relative_file": "fixtures/php-8.2_syntax.php", + "error": "False can not be used as a standalone type in line 12", + "line": 12 } }, "time_usage": "< 1 sec", "memory_usage": "6.0 MiB", - "cache_usage": "0 hit, 8 misses", - "files_count": 8, + "cache_usage": "0 hit, 12 misses", + "process_count": 12, + "files_count": 12, "options_used": { - "command": "lint", "path": [ "tests/" ], + "configuration": ".phplint.yml", + "no-configuration": true, "exclude": [], "extensions": [ "php" ], "jobs": 5, - "configuration": ".phplint.yml", - "no-configuration": true, "no-cache": true, "cache": ".phplint.cache", "no-progress": false, - "progress": "bar", + "progress": "printer", "log-json": "php://stdout", - "log-junit": null, + "log-junit": false, "warning": false, "memory-limit": "512M", - "ignore-exit-code": false, - "help": false, - "quiet": false, - "verbose": true, - "version": false, - "ansi": null, - "no-interaction": false + "ignore-exit-code": false } } ``` @@ -79,9 +79,10 @@ This handler is responsible to print PHPLint results on Junit XML format. For ex ```xml - - - /path/to/tests/fixtures/syntax_error.php + + + /path/to/fixtures/syntax_error.php + /path/to/fixtures/php-8.2_syntax.php diff --git a/docs/assets/console-output-example.png b/docs/assets/console-output-example.png index acbd42b6fe32044a2ee1606906c3c2aba9567bfa..6aec64e8841b08251aa3f21e0450078ac4c6e69e 100644 GIT binary patch literal 57684 zcmeFZcT|(<+b+udoRJw384DnN7zLzT=p~FIpddsDMOqj{L~5jl5)vCCAfQxfiIf<6 z1Ve|QARwJkrArUJg%%*$PjtTT+h>1!opsLo_S$Qmz0P0qy7Ip7Q}6q_uKRl4u-is@ zoX7Z&v9Ymn-n?<$gpKW}A{*NgrC`gNjC_H+NNv->n-oqO_$?hf~4v5dH2 zqRbl7aqx?m@wb_bXV!mQ{m9n%*NuOP-up`;w)GZ6ar0RQb!EPgY}kGaf~%kKamihz zn?eVW*g149iSC^N|FBuc{J0eRFI?i)reOq}=9DYb$C)U=6FPvKRvOK4D=cS)AQ)NZQf_t2{ zBj5_{)zbCt*@zGK)YAul7K4}y{mI7m_9XopL~-t2g-8(>b3T35BeX85jiWjLZlM6Cq{?Gmq$tmzKnGJRFRyC+YURYEK=1} ziZ3bU^q+|Qq^voBFf>>WN{fYVxox>m2SX8VN|7oA@LB=S7YYX8%}q0~qK8l8#J$3m zzmY>~b7!{2$zplIdP-Q|+r#OZ90V?ZxAs@AxLhA+0rF05>59sEReSo6uZI^EL@!$ zHN(1l|MTkbX5rp@BR5 zQO`PHhjzmkY2EBGJ=)6IkGX0t`!jmDWgl*a1#uoM=b~M$T%yrxYS1*_+qAoDomf79 zX6yQfvPhXzVeV4?D^#w;>7V2J1yT%^aWK|e>iQMGk+Tb#-aUNRj#YN5`)U8UJAZ$* z$Km9-hSL5M%RKXZ8MWcZp@!jby$_lX=YkVc1Fqr?BI4T#9>E)h>p`opQ#Wde9hK_~ zd$!AUTnMcC#G_!2GFdD^I)3bO$O-)rRa1+eo;*$Zpn?Mv3`YNIdt^O%8xz~3Uio0g z>!}B(sQZ|uwVtxOnlG+AuJ2>h0F9~bO^JM|gl?UvDqSTz*wCcZ&Kwc8-t16n+j~IE zytQJcIwq0i+-`g)~S&3gp98g1q8+WOql0GHz3yfnRm*&KpVqIu-X6$2iBmW~{n zBYDDN)Hiaj7~PaxcG6-v7h4!?Tzet+*q2BaF3?;;`0Uwex*92X({-`;oh#(zItvR2 zi>qFZAq%U#=*|+)2PlI9zB3EM2y#Jq9^~v5wA+E@8TYUCI~ugL8m_)mD3-jY-v${LSikoB)vAS~A3j(3Ss*X*s&($>_ zOOOrT?yZMrPxgX{p{O4Zm9-?PWEmAr+v;fyX9S6;<$q{&m$9rmKhu48tDSJnf*)b6 zJO?W|o|&hK8`{M`9o`n{W6qR4N6(cxmsXGpGEv;3YUI`e1xp#O-8FlXUsl9mka$3Va(Egce|6n3EsDGfd zd7P5KnHapDu{-9^&{Wn-F+GbO-S0kEkK})Csk+&iyTRg>QbTdbRHE_>m${$9K4m$9%vE2aUq_e}O zMW2nQ*y{V}AqN%5Iqepwg6jNBsk8USQe86#SuwC`fcZz|y1^J%mW@?@#(MBX4o1ua zuaB_yimXtJTwUCmZA`b&RBb; zXqnWG6TE-N=PrK;0pfy~Lv2=f?TdRLL^Z>PrbGQ87tn~_8--PyjW$JSGsh6JF-2dV zF%m!t%52}Q)3M_x_q@0lBt3-4dw7@hNei>I{lK|30TR?;LFP7mskD@0VJAH5#S7<6 z-;%Q=`6m`tF}_VoXQ*%Qq9FmcS#Hygi3yi+xes`yxP^wNj8sLhpKm^ww2>QKbywEr z;bL^{)>AWSbKS^^*egaSeu8m}{7baw(U%b-QvG!`LaN-nEJ>cC$glUa;lV0hf z9^&0Zi)bRbI>h|&z_0kh5HKw>SY3gGR;QJAUtPC=n&FHon|U!VHnv`!T$Gf&#xYE0 z6nU^yWg%|btr_yK5Ji0xIa%{@R4%PlY~W3vT`&!e@DO}Y+oCq~PdpuNTJAzP1nqa! z7YmK^&cW6%RKF*=2Um?5MVQROhD{0x11I+8BqnjsMV~cC9#11|9Hlm-n_c#oT9PU$ zcw;e>))9`Sj|>MV#$@`}1tW?rpHFA3k<0RTl~ni=KcB8eH&(2tJ+!yT=}JFWVK1uQ zI{Z>)y}7kxvOgsi?r&=v=NNa#s)d%GVViAa4=szc2&O=>xmrI@&ddcL(@|HyKk}xo z@pfg1!sJtFM~*NvT6(f{dGSID6kl81z2uzw%iJ;mA+OR+_e?436LpvPn^u(z-d`}Y zhnNLAI}&)!w#}sihI^}~%x><;x)ixffb56p`ZQwRNgqb%W5VwbF}}`q_})l@4~Y zvCjq1`8@L4qdAug3|q>&{O~pY^-u9$9Ggm#Godbp-4X(5&iseH?Eb7H$e{C3vMB>AA-D4lPi2+<&r1|fVp6TEsK0_w+bGOMmF7D-PmXw^NpkZi)-1~4;E;Up)a?iA7%C0m-K@O*{ zm0!$03v&jcI*4C4QN-u#r{+NW5XIh~*dBSnIvFs@&79?QrfK@`$Ia-!S88yg&~|2{ zaiudbEjy*Pju|l}FKa|`e<{N?;@+*N9!%}a?>X3|XqP#-d!(29xP5#Rw8yinjcrAB ze-nCl&D6Bv8`&nLMx!p_*<)vWH&aT{eM&bx7=Pn!67t;WmCc-Rzx4d_!UN5+(UQrK z8T(1TIjmyfQlZ%#EZ|(VOB6swjtRsVcs_suk}Ajn)jHheoTr9Dsx>85xsH_NejzMaW278!X6i3O z`_`Z4rSF+etPtEpC6VdjbXYvkx7FMycP`#P`09*PsSKaUW@GzHpIfVTwN6@|a|aR6 z$k75xa9zj%ReyT)Uz#NkJSkNpmD8|wS#zIOwYiwV2|4PkoLGw*Qf|2Q;_s(QmaAa< z&rVt+1!ESe7kV96v+hY|pK8681dWGVWnUd*mT*2r7rA|Oa8*m#Q$DROTxj939MY{- zOWZnWrs(h2RCs=SD$UODGrN%k7U@d{F7ZW!Zx1_!rcWt^OU|UtP&GPsH|Dss7G9(q z)`yRkKYub~tbQE&Fz@a|yH^ZJ2hXIneI7>cgMpL>&jmydbo^JBl}DABp#vd)XO|6V z%<7g&$<=0<(nhgC1Q){QjRsl}KDN2vy?U+1Aw?!cxPbP>0b*i%m~&LKR(?Qr#Erql zR7C&k053GAZ0;}Ye(HOR%FUAo!RTf!M?9pKNWCe075^yWy_$bYnAzG_*hWV3x(10xp zYXG`NrDL9ci%~n3gfQeJay%ud#7gD9M;OoJXRh()cCLv0nS~^ka@Nqp!quQ5zU~L1 z+Cxb6fGYpm*JJ=aT;<8#9Z8`!`=(=`FXqcv7t%dLZA)HdySHgftR6ekR9>Y=Q6HmC zLWEY;DrE)a_9t~h9$QfJxZ%7!Cp&$|%Iy5wX96s)o?jX3>+|5TwYr`0+JZzs{YBOwmLbUFbYHY9YgDclaRx#%$6!xFUu%sA4+ElTK*g z`>38%qs;3&q7#1jt9zz}R|sE`-Hdg2Gau$=#9rC-*0PlAus0Qs&a>NW{He2sVs@FE zBkUGM=3O_)kljy8we=7X7n}VRb-$l=3*3*u{T;f5TBSJ&Puw$kclayZjiE##5KzI!_30i!REb&g_%!G*+TYc z)bQqEdTM(yzO%vh+Q#`5PYPV1q9bUEgIKLVG=!8;!*VxsYz88$Gw?bB3DvI<^hBJ_ z>8YPZ2Up+GGZLEDH+srwVzey-xm!EV$F`UH6g|1wyzL`FL+xOCS(noJ{z}Vwh ztv520!tQfr9pnoEUH})Xp&#~Xe$OWeQ9M#~#xw5%PHnZy4tn1y{rZqLa(ui`FZe}_ zft@+n{<66NXglA9DuwK-@>aNo@kv0UyMEn}{Q8cXc?kY~q#Fw@vgMe`+tH%Ey?wu` z#Iar#cMy~Y=oZ^s^=86X^bB{tWd7iBaP4zs@60f=ud6D+PNf5I9g4$3)=#WwOu1=H2+My@ewZ=I0B6VLF z(FYjw-PK_f)nMw%z7vkQ?60pf8Zt-kmssVm-4WiAJurK4MKaO+bxvsXkoUm}^=ST6 zjJ=_u$WA#|^u6`Ug#^DYyO&@0kF1Wm-|G2x^)M5IW+pr3L43~X9mjgD?Keg`^FvU# zX`KlPvcy97;OQ`5`I_$TKI?b4hMY&fzS~jY4EJ+dk5NV+eoW3CI03q&n5%+2#-H|c zLmp&T^K?sxpu|Z}rK<>21>Ojv6kxeBeZrprZIQo0}<0W#jbLM2>U0lCgejw5RG~1MpEf@V>ul z;yMX2S3ka4caX2%=zb+A`zA=c@9=g@?gdiI|aQoS}>9z}^`O3DHe#F;fwU{BcuC2YM!A-(zTm z(UO~>RYDSazq)3_xz{TZ#i#B`6tc1^l?m`WT*U@m8H!;w4z-c}=_y^$dVe!SUcw0* zxSL(i#TUQ09&u7+|4U&7QqS1)f$vUm9*#ar5BKw1n8>`Aa7Ml&VLcd( z)a+{sIJEH=CGNXkzQtgBcWnu8(>3{X~9zT}bO0cq4 zs;0Lj+xiJ;lAP~sR&G%+6Oj=s!Bmc(&R@OZ{(Z_uDSLy}eL9Hhr3l`z>=ynj=p}hv zg>rRaWvex`gYYL#bLI$Zx9n)J+GGqv4munJaWr+AWq~Y1)#@9m-mAo5L~n(iD@{Qx z|4I;Fs;K|i5o1Q#W->y`7Rp^fGqX(_wg?@2vjII&^-F=l%iLPz!2VR5nZ4fR_`JFH zitWa{Ig!i08%QGa6A(3&h}K%}BXM!ORaW0S>pdGt{{XEH zpr8*AiiW|^T9?uWrO6uwO(H0Tp;81qIDiK4{Q1^A293e^NV%%o*ox^*+|y~FC5od} zEW9D{Wh7eNm5psdCbqoT-=H;t*W#d}v1O98g)pfg+tC3DoTlDnk9t|US0phIvS7O2 zwvY+(@T&qT+<2l4#LOAO&2C-crSrtgw}o)+{>g+Sln??=)rjYfZAkA+YD&cl9=%~q znMmFa%iN5@=t$*81BStGDxx`H>F(0eP7vYB&lx>tK6XFB@WIK2E0fBI)?N1X;K zFC>dk+DgZn=~<c-qbGemsFc8rg zw6Lj^OS-e8Vdh@(RkGP^=#V z^^LB(o&&O;fjncqh+Q~|rL%;^N3BZNkm9bDZ-Q{DJaP~^O}p)h`X>@DY*&)^dcT}} z3ROdotW7+Pi6=aPV!RH%UNQ`+`kijZZNv`V=K-^Xa5sL^+1Zm23LMGhmaa;c9T?uo zS?j#5Tk~+jqBA-3dZ6+R%ZlnoI;`)vnbc#83Lzu82@;cimvG@cTeVSN9oOD6hy|m& zZ@oXKYd$P)_S%h z2r~0M^zLM<;KHl;8;p$vfBx*7L-rnxpWwp)0@Z}oI1CREQtVBXvC;snAzfon6ed9o zbT*?LWH+K*+IEYSA-fS1>X8rNWsL0mU2IWzs$Oao$D;$pXvLU&4qIj12Z*Z(-6mfF?<_AUq-etGG5wpRwH>i z?sSu$ym_HZzyr!qBHa0=Im$vr2#oo0vs7pUw8X$K{n+63jT!?US;*S^o0JF;`&cL! zB#Jx6koce~cJr~%yMq|9T)Vkb6?a-tQUggZYynQDpl4)VC^cNc_H;f=+&iqC3wJGQ zc5JP3K_gpY4pvciI-_nrBrVq~O(+?Hq+=(1?bpsYndorq>XgXHs)}EddyUnbV+G=3 zGfzmobbknIEI!P&Cq4WK(N1};NVLZE)E>wr`vYn4x6M!w!}wX4z0SVTkd_fSLy>Wp zA)8+A3*@@@yu9!L=P`bq73~>HDspdU1oh26_89K*8#q@d;ZG4wFB({F|DN#W3;h>A zg%0qf?PbkZtzKf(gox#N#RH8~Ms`&e#wAiRw<|Phd#56IreOvnwRFJ3XQMf$-+>uh zq&RC;vHfAN!D={L@%jJ4WpB@BPERLx{XddK1Y;10=s-Z;m{q;EFuf{Ye*a6CsPX}I zyb!A>v1sV=cG(G9Ce41>@ZwN>rF=6x{VDraG-L2bdfS}k151Jyy{h!^HZxSjcK^$y zZ;j(Hd7~jXmWgs-ZWQofXlItG#>V^k(jeQfZ8LetYi9?sr3zp*2#c9Pd?(DtRcK!% zJ#Uci&n!6cYH9w^^4+M7I*Xd_vre1h!Ux#j0${Tv3Ona|qNCvX{^-3hkpq9EFnK5q zz5TMDAat@uWv4gE(P^bG$g2O6UZm>Z;}hTE1Px$-rXKB)hyP^k{IVj(J7LJenzrtY zq0I3=CZG1CIU6hwi{rkRt+I^8ajaZ4GK^M3uWBB{^a2==r5){RbDg_@J{LU-Avu2q z*@GowMr-Xpu<*u$1`rQ>@RSI^f~5>wdNM!1hS{(5H*J&hRc2q<{J=10;5!p2m+2Ru zaH;{(0Guc*BgmU$X5X<~s;5#BZE#k7)v|A~R5o?QBP#r|r8DK+Io2#+D7XdjU5rJB zJY=o`kXIKFP!0+4fwKj!qj^NVDnY;V z$;%6t-jg1N>6;e*HDJ|QSQL^EXNO~kc|;-$&rjvX1r5FU@CyFrv@4(gS;B7^kHPAu z-5thGnzS}1HDj;$bB&+M?#I{Z9y{;amx=i<0RWeaHNK5k^7{NulD!r&^5)${J-ZI` zIEM}0%^M#2wpcpy^s_|-YWgC25W;Cqp6sMhIGPF;WHRBy2}m!4>pIO=OSWlG!m})$lb@fdj0qWNZ%-M2 zFt&Rj!Z6*Ixo%AkY=52+W|FLFSj*aB5I}CWQU&?R%xDfMGq~N99tf{7u zd$K(4CM3Ixu~;Z~K?8hC)v%&Fj#E-~LxHEHn_UEMO_%xH{zm{0(5C5lP=}^*f+}HG zb9P>#+D*~saE>G#*r4(@G(5<4H*}ReFP~~xLUfc+E_3~;o%~JvYCAR9@=nRt{gy=v z|Bi!I`L})%%pyA|CswcJB2Zq9%@bFjfh^EG53<#m<5Hnk`EEE!RAb(H0Ya#(eT zXIGtsy#wLtJNivK@hXyAY8-ugxbblj*{p>1H_Zz6fKzb^_A;3Vi>uxZ8#$QWgU|Oy zSDrHNem)7)RaE43a~`~m9NUa9+-a*Fb=_UN3h0*B0d)veDL7Iu&j;5hAJr(jXPbHz z%zG+S=YwYP?$(2+P0Rg0ZKO+-jj6%OfXZ&OmYzP9rgCKGq!>b@h!zRbbuu77Pq{`U z&wFq?^7)RJW=NE+5j60)fCSZC0|-c=JVdmYD71PnT~e&>>%KyoT`-e zvceZZR(K5#$sJPE94NrPwaC_na1*XHuZdxIx2|+>rXeTqO%F+8=BvTc#|Ppo2`Xr-)FLCAB>rrFvr9)z ze*eXO_4%I1{&zK`F5*>QH@BMWMx8wvd-ph`6$D9t=jJGj0LP~b*q=hsw>*&_ra!f9 z#Q^9e*CY{eWJKlfc|+bTs#L8`&|R@hR@^- z+76j58yh+`o5Wh_phq@ht>aJ(z7T=d=z3jH5k?p=2h3(EMv%L)PZnVV>?W1WJA3i$AJ=dEt;*TsQG`c1;s zpAAH!(RPL2<^5MW6+^S{f>{D7T3`1Rw`MI-eG};Aiv{F8(=U!*VS*RcJ`iijm+mQ0 zC@hjy|H)=!i9W8mbN`KS+7l|Xk6TZ^syl8@p)D3D?m}x1R0FmMpa>w}jtxYMu(7?b zk&J~#@@Z|;t9YNikb+(gd$B)%zNFNx9MJO+5A}+`IT#*C<3(6gmhAxX1$j25tXm~4 z0w~nGAM3?+vZqxqTApb#sVRamdOWmwtBxqk^J+p`<68 zWAFX`>OLNr1@yHHnUbQcrxhOMooyo=L;U@U=I+{1hLfn(4S&RjOx9oz%>Ynv zszA#$>{EzE@Ko11!b_H-JZub7r-hy6hiQsL>{CV*s7fW-rSZ*%-M>>xt0-5#{puzp ze(|Nz{mzstz+zJOToWkGLA;FIhxq0})=16Up?M5nPGMJrea&giIG~$8+wSX| zU$iPj_qH?4vbWPn7{OOM!+R_h-Rvb;f{`j5^C7v`lKWNMW2;sCYBkXpN%6g(xM~knBEs?a-tk=4zp5?X~*UHKtXjs{7VP&ig|>!%@ew zONRFK3B=va@I>ZF7pP6m!qzDqIhJqc1|8d*FU3>fw^Vn7ry3O+DsFvE1VXujv|-Py zj08U6ue=Fs(>(~Fh>t?eO*+*%C<&oh#{ehMcI-Plejo{oypqB#I835-2NRBjHiiWq zZN3b63w5uw2_-e-{agADohH!~E!5NdkBqbE4tCe0%cYqpDUH8ZU{0y0!ZlekV)3tM zXg{Ur@1sv6kDnV7Mx&OWauM4DGr_~&zJy?*to`GQ9J`|u+?>&HB}z?fvEZrN4fEGf zEAk{%%Wl`J-x?5owwbMUl+?y&Tw(|>c-TUsT?KN2=usVr?jQce_jJqsIki?dfXLCA z0X<+WQe`AqYPqK6cqdC@XQMX%pCgm!Sj@t-_=^qv1aq zHL$pubIsGgH*Jd|K>AO&Y|&9IyAW1G;D-OR2UU~ydiyDc?x&Ibz*eSdU%HTyF;?FF zyaNbLY+a|b+6h)pLWAVZ_YC=3MVfkTJL-S@MGK)c%42A)AZNn2r>*KTu7H+F`*XP! z{y>vCdC#=hz+$L;5IhSAx}-yb5_7gDciW>iE6W4(%mCDF~SN{9!X#I4m^ZmS_ zVA}s=bpBrqIhZ}xD`|ZH3xt6Giiq&P?OxM4)B-4X*VcYZ7S#?NShgG|>=)Uwzso)2_VaU0%6C&N@{S!}D zEH6>Duz7byDFsWj4m)4_WvqvfR67^>9wWyR6N(SFj;R1 zzt#T7P&l6kbH2LFExCx9aL#YhzqJ*?dDwv%FL6aU@r7}dwlI6`kGY7#V0}kI-t)q@ zBDJGaFMD+EgdXK9>HZHPC4av%5DhUS@|Xaalo9f|)s6XajjCh6=#r{{n`XHgA=^im zPwSShj!>S_YbuJE1_;hdhXCeyErrwi&UU#wS2)@sj2x=rvvc^Nlj+xQt#?wB+43*8 zO1repoL$@D0P6dRAg1eLly(ek8aA;73da#1`z*5t_hzXtakwKzpWixa&|$NwQNr4k z!j_%SD*GNRF(&SR6Dn}mCZaRCiTj=qZochy_Wc6q`8&Io?D1c#Ro-7XaDI(ia0kvy z&#nS_1?tBrJ}ti$cdbZ)xhlW+j`cS~g)Meo@Qy4%GN) z^P=qR*Lnz%gH1=uzmze7xOTlrE?8wH)B~sDwRqww2k~8<%(+UM62|LV(|DWpaB|0? z@6#yv)Kt03ophWX21d(K1db9uJIE$5y1@Yk;nPbq*-_kHL*+)vW&tn2fG@{ym0CFc z&70)){HGbS{I=L5A~vdPWqBE{-rvnf=uT=mr+?t`D-1eAp!5R`4B8pECB@=3pB)25 z?OHCo1?{melwo_D(x-)65CA>37@oeAZm{D>) zq(!HEszfEX#!Bz$=c@{L8#Y^=uxrSH@fwycFpzH(ygt_vbnpqufQSSS8~~vIEc_xx zxfK|3_XndfLuR=Af0W;aQ{Fzk@VzhPAPu-Ln!vU9;@`rRorMAhpS`Y_(AK`kbH>l* zg?ZV`nl12FUn&%Wkv1*hhM}jt`*lYAA@4iklpLIrM+*WTB&9~6EY|6oZTDLAzWN7T zCfN+78r+v~x}h|hO(_i8+IqlXf$+tATQ5MqDY;XW7c2#0qvkbECKdUWbS}oX1o|ZzEZs!b4C7(wUsjFcz;>QX9D7@vdD|pA)9yF1E*WXB})AkB?sl1$BAb z8CXD159(Xfs_g#axeGykP1Z59UMFVO`~+41l#vBEd=SQ^l^#C0P}Ti5YP{TQ)Zg(Z zwwi0mIT%Gbi-p}=Ju)*91{7OM>ebjqLA%Y6d8cQpnM8dS%^Z`;P=;?eL>#NN(6Qsh z?6uWQ_{!Rz0-?Prq24dDc0~{Q%aMd?9tPQ>b-kb!nbXx;^Dq#uI8siiui~;9ekFi4 z8q@Hx>=CW3nHS79Yi00oD`Tl!dhbM7Wk|^4;+)Kzpn6bIehAOh_g?5x&iWv)scdDO zj1L(Qy=qW8{h+9)(J%~t(7C!AO)i(<6@E6)!8?-7E7yy+Y&^7lj;@`G%3l6vTY7@; z&Ek@QnNCdkx&eFHvR3Z&Yq|S*MzR#E&NETcNHkEcF6+RZha$7TkjBm!}+0|z?XWX`Ioo^EnHbov)#Q@HwGp6=g zXFY5N_Dsv`xyD257T3zkcT;ZDmgj%~FC0Al-<4izEN~F8HA4xiTA2U+@)IlU*|UiY z$HmY`M|VtW^b!47s5{HLYz^~H%eg*=%NYh9x3Uior4fwFrr~@CcJkMIGI1Wj&L8D+ zd6g{oAkFvQg~iiaFRQ0kCz_Th@@Kb}u_3_cEl-RDAdfvn8tAA2v zZIN@46?YGFy|x}Cq-Vf-#o0$|{ya1K0%NKpI(@I@_o2V2t-rss;6Kt}5RvTYBhNo7 zd!0iNL`&`1MVHNM(9~0Vrgwi4+L;5(u)lngY8z}4&r_1Y(jsX=E$XWiFZCi>J{Rj$ z-89b?%X+;N%!UK&Yj3#6W%b&}VG%2$MQJB~@x@UUl+OFRl zln`d%x2u1RS$y10F{k|T2YsHU4pI#7W7lRNmC^hg7-3>jy(xBRGY1)77q>KWhLN9! zoZ=lf`ds2Lx!hb88#X(lhPv@|MKFc{VRVa(%1$f(n3wQXS&U6of9D$?3CpEfm=8pv0pQEd2OOE-W+3)Q;Cwgu%yg8E<7iD z(dDQ^V`P|snTB;SLT94DS6t-jmb>UGs`oskyag@19f^{{R&;0BRv_&x?J==L>2`t% zgPy(mV?@IjG7}F!LiZNW&6wdfyrS4+B(wyN;ZqC9(uTQ*nc_LatYmCK_1rVv&XkM-QEgEscf1|HjP&PmUR_?kUY#<&y z6-$7eIs8o%yTNbP0<$yLmXe78KI5@(`4_z@mraVgETs4Jrl!dXljRK#_nrfT;|RNG zrGT3zW}{yy)O2)eK5gt$+D>KPp(@?hpz3cg*1@Um-Z#*iJVx(uv`a2&!&skt21cFk$q;$yULGa-ZM2o6B4B6CZT;U1<(M;W0ir5Eyb zV&ZU6#r$7y7~3jhtLG)5!8*Vqb@o{oB!Zz@LwgibO+l@{e^Fg{$M=jEt}fPW^yWw@ zsci9d(-C)d28EPy3&+)qg zhar;m6QmjWCX%7>X)Uh{tFLdy=dOOZKXgK9l)Up|+ctSOtx`SL46jUsv_VR4VMVAcUN;NoVp+8G!liN|e2Z{w^T|cOchs|?;Iz?@_=+j1 zg6zF;dO5(F9pBO#;N!~7K}J8!Pu!g_$5-9vKKzU0+}#*MHAlWgAm{zq>Yy|>8QQc( zLb+HY(>o~TiYI{66=GH{kDnvb%Wla^7gz;v=ET)Pq@;c^qs-Wp+u?%@c{=*b7bEkT%i%q2Uy*|*}Tg!FYZS%aMeXGrJr6e(>#LN*FV@U^oo2q>7rT< z4gmbfXJ(88vckUqszLO1gFC=>1}e>}R{mk1f=iTJsUsz5yu)}bv~>uzBrJBf=icDk zx?i5pnQmj;>)+bRL?rucM0dwW?JPYo^P9abj|%0sMbYn`5X z?+Gp6MoC09Snr(+Bv-gPo3YM=9@AmtN*(|*C>J)kncx|I5s zMN{6YCxmtuQZuLn)kQb1;&WRnElBEDn|uSeKiU6by*yz6){{al#H1sE0RTvRqf2e- zVhVId3Mli1(`Gn^S5*JTNNjBc;rbQS#H2jy(A`K?Y_YSn)d*+jYToMiMdka2 zpx-Oj?rVPO22XTaYd*Cq>6Evs@8G+a-8}t07Gd8&XVhws*#1Ku z$i6Shye#i^hC@!>jXeHU)5YwSz1~@<(ThcUU*U+T4G{op%>8@FzCJy(4q9Ch`)T#f zvQpoXQ`PFNzaG{NWelEb@W`}I2FGqK{CWA+KI1XFKIt?NeH(I!*`hjjWR{`8Gl;BZ z+QU-hRTJBc7~GZJD5=62?Bbu2FZ%KS(#(xeriYdg{I0Xf5#VZ;J2VY*f$gNoL-6_sK z^aMBZd(8`Lqm46q1v{w-Eb`klm$>w4J;YxBAa96yA_UjsNV{~0$kY86&~nuk#np2j zU78q~gNq~m`0VG8+k(#ln!cDxu62~Mvbw!LW3vNd>&k#${*swUr#am#${ZLWYOya{ z>zydTNsh};c;i({FI33cj^+o)WV_-?zyeNCiEFWH zSZOGgo{VqJmQTFG#`YK$ZXzYMP?Yv-XRtK*!Ad&#!BgQ|SI?EF{>1j{G%W6v%fo!j z3OdWgxai(&l8vllrn86CeJzj`=-8VcU(|@_Zjb4%b70^9AzuN>EpEc=DBB;epvL+a z7D#UM9|*$eYyHD9N!7VxKnJixk2yA`-@EZ4i{##yjAaQR*QT)@GsoHf`U95{2lPA7 zUD7#W@=p4$@!)X!D`)|5`!x3VHwpWnN8eVqq{v9Rw}v7SK*M5S9&F#DV>^G`%hIR+ zmpG*Uw~3qo_b=*ogluJ=wEi%r#FBgiA7gCBi3w$`-%AqNVc(7Xk7Sy^Uw(=C|Nf;3 zR}10!!BktdNz-Ms=oO4tGY*4JgRa=lTZ*+;>NKH)rRy?1`ehc3p_C?+uygo&+Y{aYHX4?B009 zIaY;A0X(o|K6JosudfDZeEu}jCp*Sni`B@?NKvMW_s6EF7Uq1b^sq3-YqPc^f{>Le z3-{GK=KT>Z#C$N=#hBb^X9idgpNlptBh_i<0z(bY<-Zpujxg@lzWSZp*0gqKSz-4I zVS5;PmpR%rs|L&3){+@VP03QO<^|SNbmY5tIZMnL%8WeP{dT4i3POcT*90isJX&t@ zdkvjwk42Z-@JimgxB0|+l59JlXzml8+d+74i!lUGj!QKu&;AK^Ldc7ecr9>CPFBE5 zKF!Sc`Kbcr*XUXi+0w(A_!RWo)T!1mClYj5w8Ja*~oexwc%qe1idznO?Kt)2h%1{ zt65cP#zjBid&;2iPG7wss`m0w_vMXk73rY^5Hh z@Z+?0DVbcj^@OQxWItJ`ox)s9wBLod;LXbP#E#R4VWp0Lit%#0kG;c<#@%{dca&F_ zH{L|$@|J#FAOaHa+V*2WTef4MGlo!A>$87Zs9cn#CFl6CI>8?=m%D9-h#0FBF1Fas zsw?bp8$7~|*iSUU9fm2eEw0*pB;&eF-N}#RSZfY`FQ}RD z9KBOag;$~g=mwaAHm31FW_Q} zZhYb77$0lb`~a|>^R_~OTPCb)^>8XSRBfd!-mCOR)0@woIOD|l?bHvf{UXnr;cLye z-#w7bevDH~&%9$oTEnE3&D5fkyh{Be1)PHAi8v#;aGV$3cG-9bm;%^^6KoAED%Ftq zPPalE$d-}pc3Ivd9RV%>*(3^cGivYsA~6hpeLsF+;zFC{)Kw#5Wj!!E#Inm;8PwY9 zA;=6T3ZwB7Y~wZ|slfRYG#0;-d0Lao-18w_3ei5(DpxR<%BoBTz9uI@xzl8mcmct? zPK7A|Q$}|;M~;q8M7Wa&D9MFyJjn;fFZ>00>9EXHe|a@wJaT@(%a;AgXy(E{uSDpC zgjO!Od47yb6~(^c?MSe-;!>34Sk}w4CcX(oN+ifUZ%M_ zTpZ1tpUKaKpli0{g>~-umAV&{Icm2$V-iz^JFLcfBzp1{Hop%%u}2yB?7NLgjR7r# zl|xzO$eR{Z`;}7p@CIT0YcFGpIA@aYO%M|LO#-B-!1==R?syeZPMlElZv3b%?t5kr zZEe4`ax~{~?cI1!F}v)cn{E5=9ae)J1ghtkH5t&yE`hB>z(hXHCvbw)tv?cWZpJNL z4r;HQ?cTW>r2hOy1&P&Rg@}bWbNRK47p{<&LL$d&iW?a+qvIGUwKvPsc9I9qlwJq0 z!}l?3j~G?UYTx9yIk>V~7X?j`zN*#S%g%t%w9ujWJfE?{AFDTamF3EIcz5qjj9)3j zM&<7fQQN*F;Y|X{?M(4$}2ei)7$Zym&N+wnQFpg;oBtmo+K*d?F(6qJd9iV{QkWSi-aV#ujJ?F*8Ng+t3)QE9+ zg?L6wweX9F-};$`;^2{%-2DmJDuQjnC4{Y{)3DO!a1DM8?^xfM$58db>erH_I#@`@ zjTbcLtum}-aMDbVxux}YZ4`#&_5kA!CWh1Jzc|x%6{luDe03$&sWq4j z9EQI7-=!FTB^wo9Yhk<8Ho{R5u~!ciQuE`9B+c#X#ZOGSOT~Bm2nCh6kcAV7c}@50 zkgbmOaqsBc7ji)icO$PSAfNA>#X!vhc|dbmU$+5^zs>ytCv0e-+A8txP0gln#I#`y zO>UTR$?ZXTBrzBzVn#iijO;^t|00$EZhGX=57=#%)v+U6w}7W+>^dOM3wM~T4VkS2 z;J{H~O<6=szw-EkgOK&nTdJh-DD`PW+Vu~&o@E%Ywi*a|mI&6*-39`0Lw*@`w(fh6 z?UnS)rxmR#2jDO&J5n7gC{&ninji4RyIYYPD*sxvFus zrYV>M=-|J}H4-o`<^;d3 zvPtMraRi?<0?*C=;rKsXYSOBiN(c#%K1QtF>9bsf2OqW(J5pJvBENs}3%K=Tt|Z!8 z@dxZ#{rCz5_vY;MDa!D_72gMddl&uV`$fy(R3ES0c3mFXllmIQDhHm4{P!;q9g_Oq z{MNrHbHD`OO+%fTNoO)2pHI2>Sp4x{H?Eni@cwqK{KjjmPaNiyp}^{I^U#8lLvDG^ zrlE)e;t)OWu%t3tKt_=lcXv02L02(&h z?YGJTI)IdKUcm(hHCsEis|_DWx6+V|1NO~XJEuK=gRDJlN-ZIS-TQ_p1mf2A>sBU( zJ19q3Tg|)2)Y~o3Q*F@_{cW`RA^B?p@xM{_-C<32%ez?6Pf_e3NK-^Wn$kNcQUozd z=vArGl@MwGK@kM$5IPY7V*rVCLRIM{L2BqxN&=w?p(V7t0X^sZ?sM*a?%jVV5A3qa ztXVVfyfX`1bH^nmiS#beu8rTOnWw!OVff{Dt?N$9R%A85h|itUVGlFKrSBVgv*H-m zRp_RY9E}Z)SS@5)5txNnx$crZT~}=~&BMz}ieOz$Jj)VhTl>w^Q1K^%f%`cDYbZ)- zWqL9h9I(7;0gpc;X?HRwVX8CVpI;=c4!Zp@plV{by4WcpX)ffL%w_d)3Z78dUe`a3sQS)4cE>5Cpv#1v`r-Vul}exV>qU(pl{u8)yl1FYKika1(xEW zY>KMJUJXWKfI0`kNyT15`X)}qY!+}snx6jn*(0r;wHpn^WLq_;!F!A{EW53L)|O_@ zkP?S_*(x9C18xeUF+^l}H6FuNuE2(eZ|xZhbjm+r908gUzu413ts_@`7~OEh>iQoQ z0oa}q@$!eTT(^cF-W2o&k%qQ#iE^_xRRdhj207}OOp3Ub=AGTU?c$;(qLsAxnmTCn zP(be%H3r#nB(3>#NzvAlO~5&=cuy{$?TWh6pQxga^81>i(4rA`RPm3=OCoz8r_-ph z2J-o6+LYuANtI*QV@F-RDi9@}cE%|+qY>Y5Q0 zzu&mGA=A+vj;VxcLO)QSxcaSZ3AF~11NT5a*8t&-)KMJD#k9MVPObP_?Rz_=7fiQ1J(O4Lqsy(nk0JS2C6Ac`w@)c9rZ zQZP9b)`+U2W|%I)w6bl!8^v{)xlO7}cc^6UZv5I*QXq{?FCiOFNum8fpWhhfp+{Gt zEVsPbdre_$1_9Lc>KLI&CxMu~&x=+o)pdHin1?5l*7qfA|MM(no>rrjSwu?nsaRbE z*D625@KhyEn*c0LPV+55Z5VZEdyu?;w$c|-;$?cH>vdVo>fOpE%TL`DICnw5@X4=g zD*mmM%{YW06_Cwwiy2FO{Ibk4;jK!mG$oHoUJp(dvbk|`3s641a-f+`tm6_~A1*Rf zaj#Z#F=BtZxwNyz5u%mfgX9+LeCM*4mZ^?v1{zBX!xAqh!37Y?y|F5!zTp%D*4LnA{e&P_q2(rrP?f7z^5)9th3fXQxuo)-3qko~d(VmXhgS|82_ zPpws`v)tgFQhscLclH0dxsIxRn6b={ZWK7q7>>^0Z-jEu%mG0XSaG7!2lGS6rEx&t zrK9`j{DE~uM;CPOAhD9}C0ES<+if-v09*fZE+0cU^I|Rj@=9ek{mJW!@u~Fm=MfoT z7X>@5BX^ee^ovf@Bp70ws2?FD{o_aFZ&dmnw3hLE<#82^(Ic*<8JVemNSXNAi+l3P z1-ukGY@in6=7aV^J{=4@whc=6R{KRyVq_BWUz*jv;~Lm0;KU!TTqZsdh7a{r7tP~i z=&bo7XwqS=SKlTPGM@CRZVLW$r2AJ(qo{M>!QgxZq`zQ=C!E{f#+!!w!JSl;eCAd4%7nG0vLkv%;}a%EE7LABtos zVg|~lCMuVcp*%>;_b&RoiaFd1?5z*hJa1Vx^L;QpmN*&C98MTW406-5cCmM}Zj1q8 z`h#}fNE^VBZsD#jCp5`-)7Q^ZP1;kts_uPBNWj5lxT!gtmt4y){zRA+K3m#dsEMhM za2{`j8l1jjm{#Qvz90t_G5H;?r6{rcCg#`kGHSYV>^~S*$9{JIZLAN7v!jAe6%w4q z@EWG6J|Y)8uisoLZ18!>urXGVs$0o!L|c~y+JNNo8I3laVj_&d_)D$}^*%krfAf^z zQd?$b+L*TXe0bvAh0pzlVkgRqx|2ohuGhKOjnBh_XvK0q_v*Ar@!`xM1rvcY9(x?y zpK^&Xa%5_AS*hp3D6bn*__?}^@YZft11GUAH9M3c6S`}EFPaP0D9>=leXN&O-qhi zcOMQsM5iqEUQW@6=FTsbDpTX7VPD}YjeO6gUSm`)vfNzPYplPy9<`XH0EvF4&0@yA z;WFso;HDHj$G~MFB~i-h;xJl9`7{+sSax_&>%Z!{X<{5@0OGqTqCkE2{6$yUc=|EO zA|2(+x1uvC74M4OIr(m$dRrQ6<@?sY!F3%kC6@@vKYZJDCe7&8l=0isgEb;WMa1{i zcJFi{y*#PUKFX^0hD@$2`VB(@lc9CwqCQFYQ2!nsqawq5HuIkpYzL2~=i&)5f~8;b z)&>~DXD357i1VWO+M(keQy=@4m?WgIqoR>+kOCjHT&#-FGnXKwtc3V7Ki)W0j|&d% zK?|n4QDmjk<6iwLi}N-66czFgBg|%uluh7qG`;w}IDG9>&L`FO$Mvh_x|Hap?FS3K zOR}BVU&W&L&W7yP2MoAe|7G}tO34Hx->4UqzBfW<t`yvv0uRY?D*0}&q?Bc$1;>-b7o=B2MABP{YQ2}nz%CIyYH6S_KsG4A7*{+&E zBM)@A@i^CBzQifQj}t4e?XVgdV!R?K{JimeAHCz_r_*6tH#@Lr?l_^UN8ah-3Qyj1 zMy0&^Fm#h<;^1zU63##MeuGIXF@THnyG@mWZhSDhvby_LscH>Tuq*WmHUD9sBRthv z=u-{PARv-1$zB+vTyvzdxZ@qjg_YUfr*T+$fW5(>H_POyXk+4%LHr1DyCv@724YxU ziZ1L`4Wh=UJox-p`Z^j@Ey{+|4}a*On6QyNvOeCIJgmJde>bhP<7pt(PvU*TVsj7? zaRx8DdJoAYKC4?to*GT871RZPA(=l`mok@=uWY(RO^?77>_s$QE_TZzbnb^#9F1D= zzK+gmC{3L5)m=hO`=9=hS?h+q(BR2WX@C-V?zq4g`OCC{{a^Oax~h1#aU2B7K1-)Z+k z4!<4KDP--_Q*}GX*!d=^G^Z$?Te!K8`&VN_&yCY9XYBg&<*t)#s|-Fpw(sS;WDa3k z8my~Z4IFu$YNFUI;xJ-O1kkbxg%Ey z>g-);o7kS{(V}YSXYd|T`?V=*wPs;$nT5G7OppA$(+q~?lZj=9wV`GC2}?@?Zjjmb zwU}SzYs1TPSK8NGaY9E!4i}EL+_#YZ{cFB%Xqj;5 zatR3)Y%X4~T-xE=ZxMvsm{1Ko)xU3cP6Jo^9&;iE(qnXc+xWM8udhQ~RYAt=pI+mG zXu#n&b-gY_Tph1e0~&u=poiwQ8RO1bknTsn|zPq(Q;@8QCwe_5p zu_gNeVFThL9FG12i_S9_6p_a%A9oS%k+>)mzONVX#?Lq6ZGulnz}mdaALuAfwMS-S zPMG6`(kc9Su~^*R9KK0RH?_L}leZQe9yd_5Nn;FWho}C#3sUW)FmjVJWRW8viXIfe zCo$Wb;4d`Yiy~SV-iWR$od22dR;fi4n7W5ThD~z&1rKVq2r!48)7#J9-j$FjbsZXWpVSC}L58vq(bLNS5<|{2xLrR@$d!Ck-CcT5 zhH+}nl&esYukNuyI#r@v*Cog?>w7Hdtv390?okT=_M$I)F0fpy`-7-p`wWm#?$~T( zHkG~B_6q9C3Zh$v+x{3|0q=1!eELX_v(Z~l(Xcvdy!X#}-8F>f)`N^}K*OcVNJM)+ zF?u+(!@-cT^T?qeB>*b@>8dz|+KrE*>T;3H?L_U|leii|IKWgNP3WY?*Kg2g3^4BG z*2U&t%J$~58}^8mGHQqcp%Mb-X3Q9^zWK0HTS}Fk%Ef*7E?|XLWvdg7KaKFUmkob3 z6IJNq3~2NI=do<&plcyVA`?t7MIs_d>jW=c?6^5DaZDzMYX&cG%_Z#!Xq>}HqVp@NVg!G?^`6yiX<>&XTZ`ucxqOiV)kzXKR`pV46T8dM~Mk!MtWu&liuaJMFcs!~5_wtrv-+f-1ktCiz&k7VGDeglKMpKzM3T@zP4)g8{|s?dOjm+C zkeLGG08X@v{ih|7cMY`N>esP;uxN9cgsWQF-0bx`$OSJc4%#AXExkp@uH?>S1YkzB|)8cXPws!s7 z$hsP(raW3!G zU!kv=D|(vzz($xBp$6I;sucPUMhxb|8aRlUL5Nd-3PE@NDc1OR?+wp=X+ospTJHq# z;*CDlg}fUdk?m8mo`iK+O{yv#(XI0`JCj^JA{fv)xx<9U=NgzbIb~e{*8H{-X9l@9 z!BMDTXiyNEE0dh zXJvYhCl6nBgN3n|s+)2)FbU|zbY2y$9D3*|#l|GkOAHqpvLMXOSnB#47(Hgy7vS%I zZZLnFf@QW$85BE?DpZ|xwNXDpskCPbm3eqxJGfrkSj{8HqQ6=2lm64U;U z=7J2z%|bLv|Rq!lWsHu z?1kZY$G+6Xnxc^ry90~!oO*htTS2h(B)Dk?()D#*o{D_BSQ|>#XKEhl*nBxJ9*FR* zF|XTqZwWJFghWxe-Ff;N*>AHl^y7<{x8BC5(umb^5vWWfuATn7WJKQ2stgF-TAI-5 zd&^-4tFj&WVR3|phS&|hLo6W>z+Y7!ngTChktcoo;v1g<=Grj_@#?rN-(Vh2$l(8v`nd+fL-*kqy085IfiCD)R;|Hj>nMw!;mm!0YV+g(4Q!BBAC*#n0;Rn+LZvQTW|M0Nn=*qCEF2d@s9UY|DYO`<)?5+;)sW; zx`{f_mCrH#MTH4!&#Aotu(&<`*}`YeV3&eNT3l2bqf$6KQUlC-GMf@JU^`vAUS1Z? z>6-av1qAoim3Db-+Mf|~cV$W28=YF0iOra%tV??JP1sz?pcdr97WIs+g~!wT)F%@Y zepxP<&9%M8*AB@_OdSCUOM5O*50K`&pm44~gFb3aUJ14{wY@X zL+^R}olefN-3^r}05&54KwZmO@>_a_#Z6*VM_eC%K43DC5ZZd9GmREU*e%M2eE}ly zVfGose|JItlT2CPblY2(>L(dQc8G|pk+F`V*DOtY*QXEGuCDvR`b1QQ*{0@HOD6fD zvc#b4&b+~y(ao1r#SV{dVlfA@ntf%N|BQ7%w}q=&oqa0<8ugWriN$_YoHR6@p?~vn z(N@1`Y^C=T)bt(H2MRywKb~?G8+??1oYL5a?1+<@1mhqFYeYi| zC{oS$S+H)$!ZpQjFqmiR)s!3%CRnUox>8X8IN5t%DJL1!6|fhG0%EXC%c$3;0I(}E zM&qGYSMTA#&73~HqTc|_@;Drx6ryU7rv7fI(!Zy#M?|ce^gq6%xc8ma5P~7C`wEwQ9C-E5vbfcPJp31!e6sQh=Ft0Y3FEh%y2kmABjx7VzlugJstwtF`VW-(^3Go* z^T~ffGWSSz-Oe!g{U*>|R=9~sxxKjM-$?H()mIPz)%!7f5E6L_{+AZKevn#DM|YI} z6$SPDDd88mnJIy>MP!=W-zmI=&HmXC#-}~A@<>_F!MQ(xddz$P)ZtKrM=7~#dhiV| z`>UHYr!XfDtLgr2vOOP|f_jTcHy16c7V6e1U-DZL>GpCTnCzQ-H;mrSRu{}~W z9rYKq-GAllT!xPehNb-7UcFln2M5~9G}>bcLSY#he{>SpX`APYmp%iEwZf=xym9(qAP|9S|T2h+Uq=$+C*v@QBhB}g&HWx-^Xlp|kv z=3uhG?m%5e(}DGT0mTa^Nqw$)CY#GAhwc9kQuiaz7UcI}KcuOoSlxS2ehl*OsJ#@A zU!v-&_I-J52WkQOW&g~0b2&_7qRS9`a4gc^XaUL@cv#)f0oK~$wlwssOfmRhjk9r3 z?5l(Q%*KcnsSJ*gt>2qnAkarHX~q7lsskSY&kL7DZx?2Z5P1@EF5_aBRX1JEuc ztj4I@VGL_7xn{fM-1m5%8uv7@$$Pt|OV>z)^Idf{f40Qkx#4xiogY~*L_TaUpBvvD z^NqCqTyVw*vag(!eXasUD2C81J&IW)E$yNY->9oE`)@@}+rwIs#JGMuek9q9p8&2#V2hZb>~|s?b1)xD-o()FMdQHYSWf3Yt-V=3H%7%V4bSw* zARMavVa zG_maY@QG8+B^n``>E9_9FOuf&-LIhfB1D&qiuCcM9irK={oa;K#MTMZwrQ2yo}(KP zp72JCH$sGzA(^6x2G*sWy839-dSji&#N#F0G>aaU-Y+FJ zQpjurKOL0~QM7R%9ryfT!T0S~y#xgnSN{-OAZ{E<+FjwSA#<1Y2rB({F_#V)9d?wg zE_NB)n$0l$@}=a16A(2kFCf6S=km^vbi?I7hB3>C%zvrLI=lbz6~5#Y(24E#<7I4) zk>VVBi|GjeNTM@B7*UoGLQZ(D_h=#mN6lng1%J{X;;imtxMs_x9aiMR5~+5q!Wn z`S=Z_l*|-N*2QVb>GASY;vHynIlh)cC99QU zldy)n#RhoN){A1B6hpxmdXs^ah7>t>i^}OX%@BwUqg zZ2@|C_s-}}VusHc^g1*-YgO?_n&*1zQs04UnU<`)uXss#P7X#M01Pm!cC)@xUB?JY zbLlpsZRp2ya;qmYxmiLA`Eby6em;NBjxlqu?Yrr9>L7k#YXy+kS9;z!B$Mg0D#2o& zDg&k!k$m)Uojvvqgp|Q4fban#dgJAADIl`{!}~I{^gC|5dVi#pLD7Ri_UxC%PoN0q z-E{%sXW=Ei&M<<9wR>wbtPPM6S`QrP{6od)1qgQ(i2wEnHE^uiYhY=lUGC*2iI^1Y z<2^fAhCCYixYcf56cE#4nD=`xq#~b;d_5vL(4jR(3E*ut7K_w!mW|`m1AZ z(*4%uBYv9(b-sDDrAFS2?Hz4`n=r!B6|(~b@l&7oU(cFtQVzM@d%#<#dl?G=&$mcr z0==`5Wszq*<{~aBQIkMMANYoUrRnNgM+UOV3S)@iOuI5$kue+Zp4i3dNic2j$6kaS zFzN)<855Kw*&VhP5j%;qHNFRS(SS_nUlpd=L;~vo>Nx1zk+vrNF#PH;xxcphnhz@{ z5U%db0%!3jyDW)SpDCP70VQXp-CO2YS=>>a!Xb8_08-u?)wRB`7k&;$*FM;mwr`3D zSOtF#LF3rJrg`clI2PuQ@LD47ny@JxO#omP=bP3Dkl&W}ar;zIs~uGV2(iY4Kv(o6 z^s6DQ6-LYa{(#-jkg?0z_pPl1P%w3{dz+2*5%7s$!xmBK1GhoByzBEa|HR6?|CPkv zIrF-n<-8{VwbDkhZ>zX7ZR4H`?n){7#7jnN#nob0Le*q2p2^wt+Sp^O93@&4_yR6P z*P?tkf@Fajt<`!MP(N=W#LqdG1}gPm?U(>-t}q{zl&FvBK1YSX&$^|`^!xj)x5?YGIv)K+cOUFF zQL!j$Jo%A@g4R zysNVdi>`hwGcLgL+g+Iqp94>8Of-oG><#Tw$P80)se@4dOJQKN65a`NDngxHq@^z6 z;ugiF*f)PGPb@&?82i}2Y=;7{;?9}GQZohOM(8zekvx3JXQSCO#4RZDf%oA~5)wOv8fDM>sElTH~3xCrV z`Gmg(323_^-}Q46xArKc!29kfPTgN`N4jtPwUrvWUoE2^i#3tfT3v?%p0Cr=m#~?3 ze`Eaj`+&a!=;?uXvl02ni26S|_dJSA$wv zf4)XkZf}X)*iot=DQ7Qz`UO}1`_+AG4zPVeTXXJ9mVJmsj$FAXf2~;S_q|hKW)e$q z4M@;&Ue9*~!4i3}1+qJ{qjv*3TLC-rphcn&$;-eg73oRop399mMk{A^G~VQV`maB` zU3R8D^#b~TyZ!bcFOHF=FB7aJ>A@=D+(cg6r~40-K2;KE87^{X-k5L)_+L=u!oM0;~SFLoW8!G0& zN-lG^{QXK2cbYd7&!o|UAr0ZBBIr*9eNTsszczmIji03T^h7(D;^ONr3BEp&nn`f| zjVKPNM}k-3#y7iOKusR>m4!D6miwuE2^D>t+x_n7RCzVlOMd(+BDeC1!{C4><>&OV zzs)$N302f%7rkMFc$Af0ez|EG?xl7=ii2(RFO<-tjA}>bsx&b#E0S-O7S|gNcg?QC zrAZ#ZVKAnEGxGE>#V_D5??ahY(@b$SxcWjd<)KH?(12}qKN(XWpUlcOdg$*L)ilf$ zq2^<)yN*Ne>eE||kM5(nK*O!mULpu2kw5Oy%Xcb&9WO4xp=e|I`}}{ip8t1bY4McL z-Z2Wm^;{G1&<=k54|6>`iP{^$l<|+xD8OFX?H&-A*4w-1B}X72;&TV0r|bYo*-M3k z=4hM-{{NzW&?3~HOSpC*SX>epBExj3(yC@FLv1pv(f^W zxhNj&urGVL5Ss8gik(dar+|6M4k^gGBMqk6LM7$75zPP}dtu4}C$w%sv@^Rk{Dr{= zP*QCDSc?}<_HF&x3x!o-yHRNG^@eXM$R?lb}hSumeiM;@tEkbr=%eA0RMtY{6)*)W}-TDMXzzB)_1={OWWQdP%o z9-5FS@$g_+0)dtSsDm}y1ZUvVLLK2z%f?Xq<*Z4G@z~GnpU%~sWQanWvO5O7U*=DT zQWB9u+Dnxf**+`bZnorgp1(`uXb|kCQ42D*#s#BPA?8T7t`p@#keEyr6D(P3axxc3 zGPY+}hzY#=-qcIwjcCxI+IkQzLqIWb!D{b7*G*&yE%s$3%CoQvSX%WYsJSq4)_an%3<`;Aa;w@hyA^+L>a@ohlZc#s;ZQ%l6Gxeg4^KP z#`pH-Y+<)om=iMQK{nOdRTOW_< zr8ytyTxI3MElAn*Ug*}0OqcSd`L92uT;>(Bw*U#lN@cXUE3$s{*={N$h+Kq7%M5O% zc07)^(Hu;YhvwljD*LMf@@3Wh;+a%ahW#l@mYL&V87VW7kKzyJb+&_v3M=7pFWw~$ zb7stWxx|>>TT}8Y@Z8;*@2vS`jCs?v?U%x!AR!!UI80)KZc)7p?RitD5vc-VqG*^X2B9d#rC4m0;biJdjX~y5D>1C5ADeZqvb~l zi9ai;CNAS(Z>8P0UR^$mow{XzeEz_HxDc`1?*v_Q1y$CfzKAF3Rp~0&&h|EgJj>6` z-}kOUR{oL}Pu|wzHYOlVE_6fHoE5BFV0{5nyf2ajyhQ9h1w`4>s7t=ady~Tu!Ui@he^S ztB-M#QY&||Dd(pSoxht*a<#8A)(1f{k&30rf+@J1cz9#z=rZ2))0~d=U`d4)JrHa_X^Whi|s_x1& z3EML5N6E1RV_6sTsLnW^0BvIjVu55?1=zFCQVI}HfIEUvuEEgBWO@w0cQtW#dCK3hY$ znN6c^)XdcK)<-F7;uu!pAZm?^LiTR_&clWKQaI$6kW{jx+ygH7K{^Q?>X(V4&3)%( ze>c!#?h7Cyjm+r0WbabD$|%v{Ey$^`CQz`1l*PL`VP-kDgEL@Wgi*rn59e7-$ak!8 z6CjMRILqF@z2E@2Zlsb8Sd=9R~mD!t=`;4!>mo31<;;bs}h4}6V_LgHkFMs2#K4o<5k+n+`W5v~zF1T1 zi?-q2t!ghY{pq5r*GOXF+*GgQ+;mh?lm?_(&QbS*Zp6JFxAmG_!fSF-wyYY8Dn;-r z_C77=*k7iwmYowGGJChrdeEW*h+$u*Ji=PC4>8HJjlNRJN7}prl*6;yo=0|>#Bsg4 z1hdEE;g?LAb1jT&(W##r(nL)AeK#J-)K4rr>(+D%cFQ1W&r}{BkGD*TtgErF71D(S zKrT5cPF3f$8cNl*VVMq$jHBNXu$#`*?yX}##QuMc0lKdo2QSH1lLE@ya=9EBWznp+ zDf?%O07y0cUz_yozfnJat7X)O?~hcC46D7?YnlL*PY7VMW{wE*MQ^O6xy{O$u>KRa zmvbF0dIOOnR#oEQ^eymBe=uksFm<{Q)1T*T!v-CG3+m)A&nV6be7~1aOt<&a;aXAI z_#WRzxzp|X;?n`-4zZIW(w<+umclJuPx>&x%%5rn9xcjYF{oCnmWwq~HS$^rwi&d% zk;kfEAak*qZA+;9C<)G}-p?)-C9(UdM;}zpbUuC~GM+!JTzBrogj#^*lNT9DYFn4z zx(j+)r`n`^6%a_zsXr9T5zc-BKw~c@0=DQa@qMac#e1&iX$N1}j~@UF@dv<8)h(Fn zm}%_cWgaerrP+*>CK6AoGyg-4kF4;#E;dN`7RsI#YRO_6D`GC_Z)I_^O(+JF(;9P0 z9UvDl(5{S|8fCNz)na(~Pz=y9l}T42SGC3S-}Lomwc?Kan3X+)o|F0_ZLq^fcJJx` zxMA5Wj`^zbNNo{*XhFlWi#udhNZ@q)vtbYV%4QD0pZ@KfN?uJ(ak_FnSArwYu$vd5 zSJ7Up!=l64~ zVSp}+1PL)EGE_N4DN@EWdh2z4}g=uf469E8+N@Y5n^xqoC+uz-_SYDI3mDv?*2{JYx+qhOeeE1FJO3a_1x>3 z=YcU5dm~2UR)qKzzDr9pc<(!R6$n2gNK0K{n1tAWP>-*Srkj|KpW;8gewx%$cp|&f zFGgu6r@yFd+{b6F{3*8vi@t@T)9fDzCAx4yrpHEd4pByucaY>& z&+A%3bGZs@-^XQTuEly+b(Esl4nZ)e)hJc0Hl+Mr)<{|&z^$5m#E_Eu6ZLS9PFBif zc`4LpbVWc5IZ}<&CQUx6n4dIfCQn?oDqsllQbiV7N0_(_JPwCvy_t1I5wA{cCXY0a zpI)PL|JXlR?f8~Lioi2^4uItE|2|xgVPf;JAJkJ%ljPi~_uxK3I-9OkCEI+LjfbT- z@;b#4ztQIyVb@=72{5Sz01j65Yn{Xwtv5toIBmMMGqfErZX4kM51ak+W97VeU82mgYDxhUFBw zm-U7!ti1Z-PCf1>ZaOT`IF0W4Kl5y5*pM&gG+wY7(V*lnt+p9Jk*DBXXt*4rNJt6< z>5-AS%qoXL?$IL+$=ANYG_^nW-(!o#o0_vQ+U2S8O6-x)7F?H(`O;O!s`3)onu^Xa zN|m)fCwMC@U5{Znm2O-ygHR7GuHh?TX2K^UV;1uQzrvJ4p6v`m6_{4&tGH3U-Om|f ztBKnDwMPNCnx80x&`Ct{S5Pf@*vEB5D@ifcv~!M@R7n%q%}R?`6)8p-;`?W;Zwa_$ zy_*I1*l8i{&225=U#3Njr>0{5&AV-XdhcD&XAudf=cBbPCazLR$7gO?w4y|G8zo*~ zY4zjA7A3*Cyepnw@CcQ_d#)(M2y*T<%s(M^_02;%-ZjF;mgJb?=w8^c1x^F5tL4R*Ub{m0!d*k(vPG8fO&{*mv4bqmj8de_sL8V*$o-`V!V zRpsE_deA=U?5!9kaj82H7e*5jm%fmfELmYRb>b0_O4cJU<2BR@Wg_FRP7K|MftM^i?}KNmnAT-1EJtuqXHUDfC>{v2u9&v#m;d z*#(AESmWrxENW*QMz)vp+hn@Rsb-}rT5}Li=dgU-6{N^jQxMUdg3>h)I!yPnmjVZ& z1Ws7^ELXWb%g=qdm@5)RIho4aQ_WEX%XBaj(7KvrVo?@#$^f5E2A3ro;S4Gn=`E{r znr2*Q*j>Z`Dz1c-maoo#5O6om|33-1CYAq%fcr4&qKi9pFuGjYNS4pu#h#5CAM-nT zgdnEFb^JY0Jo+(HhW;pF zGBb6d#FnxNOj9MtdFBdYy9@Qwi>enY#k_OoujQ@`=}AD^&7Zvr1DDE~VaAj#IR$*%cSoM-XG7>h6)R!=(m} z`~d8ajb$w$-imBh)cnvk`NM2!b*b>8RuJ>$lv1vBaY~s5g{oP2f;e?!g6)HM;L~Y# zhJtQy^&&G+y4drZB6om%3odsGzr+Jj8&Dv7M^nrv0g&V8&ulm;m+!;7oF&krjrqbg zUq*oHq!$Gc%i{MUlO6g7BnQ|E`qCKgiDmy7(ZPn}4Ac1q=YrqzTO#{?$c5?x6Il|t!0viF#y*4Xl!zP#cVh$4CuNOjSerCElboQ`|LB@~ z5jVE(skL+(`{*SNgvefxo?uczqnDr6;=2iuP$a{t)>Xsj2{^e}oR3=K)3Afylq~bV zIJA^peEe-5&x=dT-vyv=Jty#2MZBT4@)r#{R5a^@>1D|0oE)B=z&eU}^Tt;!43^Z) zw8St{i6!@sG&DW}WXPlB{2@ggV=E8@W(a-t^-*adeH-@WW->K#}uEzhW>AgFpW>hvRIUhUob_a-;L?Hf6Ib7>~IP0ME&T}MW) z8w|TOvY5q^`%h5l^HLrN88}zXQZ_&8or!)NLoG4{imS8>v2-qi2cw_0e9o7-4ir9{ z?9vDsxlk>6LcAS|A=fMbNd-Tl7ffe$b%Ycy<)v36rk~_F%l`boQhEok`^a-|bGK8Y zW42`f%0TZg{{8?P?480^qEP9!X#KBPu1JPccC^wbNj`rXkI@y#ga zi#t{2lzkBS25?^6t#h|O5;m4}U2uA6Bd(OvpQU;e@=_3J3o)6h-B-|)PY-g|!nbt` zN^N6i(%4o!_YzQiTljE~!*{N7ldL$nMQAMVX?qOR$thc^?~V0xuU86mW~$8SdAKGn z@u<1Ttu${1Yh44mWF_MT>-BZE54(2wp2GJ=)pyccCkklOJBwx23xI{o#l4XXVnH?= zY5cCdgKJm|dT`Fbm-K+sy7mz~>-F3xg>f2JnIS3+mJ!+2=V4s+>x);Wq=i%04$XF3 zKcWljCwY(s_Bq?I-mJ8_gT1#=yx5LW$I7~e&=z@&2<;I(3rV{!?0rV6bbbITPGfM! z3+bhD>!T~llf}s$N2Aegh^7`lIe=DwE&nR-@T5XP^2KKoC)ccA5Elwp01=b1STU}7)im?;Kx2>cd!B17A(=t4wGB{pt9 zS$wCKckG|D$Dio5#=&0fn##yyrfe%0Uhg^E*4GZGy)Us@(MeFeNS|3(?xDjChmz#s zOYeHQZv>g6CtU>P&ePGga7}_WnD3Y!%G*?*^r{R=9dcM_n=Ong`lxs0Xy}n!-q!YP z&z?CuD%ILqOx9pIo22f-)SjpVUUG8i|1As%AR2l98Bdf(7$5NS==>(Z`eMX%0=nYK z;>V#?biv8LF`7!HWqyIbVISRVh$6BKK=C>NB4la|@*NB@#7At9eaW38@Zg zPCZO=k<*ctUop$C2S(B>ce>F+sF{sl9Hl=xM(_$hEg%^7PpLKV^H-e0{kE5O894fCC&$lhgT5Bn~bUG#V zD|L_CW1q800$)&)tFgBBUfKLZs-SIW#DP#B+iBX`Wv8F?>4f!#Y5kYRtzNqHrq*lf z$D@afA3v@&YOT$WUAdn{um8COdin$$s7;VRJMw8S%PRFs&tMXp|e`OUFH z*!5@_xm#vRmHsL~8AkCTik|!X_rvGaodSlPfn-o2Rifq#wYWBRek=tAs|zO|OxpARzG;#nV)tvp3<7D!mnX?W1J z(ZqE-KWgkF=}yVlC$A$aiIjw?oL}y6DVs(bC&@0K?%ezE*NnZ0@m!<8>u%azYn!!13{}4X~KESjD(cs zzgfWftY$~9hbI_VZ2FEK zZS7$u-<4D%_TrU+ZXeSQea8FTH!e8aQfjZKpaIWHH~!63W$RR!Sz}RM^6{ETi3k9u zu4D>LOZc=>)cqw*t>Y;GRyZ?^G;9ADjOnPF0Q?Jx9<^ z?cGgdd38_Ddj3uQE3IYKRBK>-oj<2rsb30x6Aj7ZPI0PfQ46hJRDqdx+$#g!Gg%PP3{R6Ge$k6?LgU z`4Kv^S=sf4KV1Wp!g*|fm_RC!YsS-APV`+mZ}jQr@g92Nf@90&Gr=(ilK+Ahroi7bgc4ZOzqVnOGgQ!cVFI zNxUP|VV4B`ZtE^e`T?1n&c+h4g#(|8k(a{zKYCvPnADU_ON=T#M#})k#q{|+XKA0* zHsznknwHImg}HZ*saRtEooVZ;t@FzKPwp-o-nPHve3x{T$d?|YPqu+AhoasUyzqAD z=TsaSxZ$x#KCwOZILqTQLF$g3x+cl$Tl3);Se#}0gEWxD8xzGT*K}Gc2;-9C*p+Nh zZ^}qu>YmAAijqrn*f%J4NYlJBG!5*+=y?q=4mqK36!&Sh7rKdjOz1LHa&|9}f9}apWJ6 zb!GwmwM|1Kwamx@p8%Xri+U`{8H^rO>M}n(brEX{MgOSdonFM zNz0}6It^*+jI((=p-E@XE-V762Fk}~cBt`PP!+=0%$)JYrLr~Jyl-PT%0gOwt8SmZ zmcRZZM;a0POZR&B5-oMrEh%H;+D)0cm}iRVBV*&$%;u;Ir&9q`tko571tl#BAWmj$ zFW6@?n%ra!6zgP_E-8xgewRHR(uoM*>kC zBd)T9%Vw)PE>6`#tx)_q`v^ z`DXaST3Ksl)-y9to9FkN5#;*$8y@np_AvH=JOwN|n+a80i%q273O@1Lrd^HRZ2B`M z0N(1b5$yjnwqA2dSdmH-Yv=LaLm2~XZc$WU6@bVp?GM;n&3U};JkEkBeUe`U7;?%^ zT}gZiWkbtqO(I$g?<#-75{xBJeR=_HkvaXK(iQ3g2}G~0_5n;8msS z$2e6#E*{_}Dr`??3Lr0agaC-0ut-_15XoA+PBDGBfsFZ95`5cG^Hz47C(_JVz63Gg4#wrXKTac*J6uM zo?zqnH-kYp0MQOcq@E=nA7|Di7jybL_y?##XGmsF3*I+q8_v+g*4vwQ^pnK;1FUZYzg;2+*L=WEB-D2>Ox$tO5zM+1qPLZCo=+ zd82*X0E}i?6lVKz|6w?HL5W5OaN7NmnB-R_teM!NsEj|?M3Gt?k)Z;Ovy5UxoO=C} zcYlZibi8C`Hri1#veohOqLi!6x0T+iDiTl(B?wfQn0kefct+ebCJ7qysRb;T0BV!a@~S0g z7EnEojjPQ3y1SkKT_j{F-#k%Gm?^gV#+Lo-B%>Ub0Go$r|H_gP#g=@f()3H;w6BAy zl=*gGqwi{nTN#)-B`jNwh-5S_gjnS-1>e8VtHz&5e;W9@`us)q-kcZqf)*|Z+x#$l zs1lSZ`_b8QQCcPWRVC@YfknI78U1_^_T1+x5`Wh%GJ|ZfFQL-QaZ3&A28&gkgRPJ8 zngP&;2VJ-oZ~Ro>@ldN-`;`*Al) z>~7lZjT;4N8bWWypb_e2o@NT0(VElOUb+62tDIrJITDM0()v)rTxYis32mX#)uW1k z@wDU3-LM}0;=G9(8~)+ag+CHFhje_o>$v6JUYnj#$d4Jr@(_~Ek}o(wEVn($c8pL@ zCrNNuat-g4msTKj#5%sd)sN2qE|Cfx_vV&VDdgbC`WB9oHt!N$KDq3Bk zdeXjp^>@X-PXcAsjn1S;yK zaqVhizs{|~u-N~yRD4(r&*Tr9kQf8)NHj4NF8?AczxV<5eeUo(fvfm9ovdkCg`cJS zP1IJXCv8H$&B>$qYw884Wd}9fw;I|`pP&G!nlMIcneFgp3xF&*pDbXgKF~eTUAq4T z*t6$C3}vZeAdX7x4MRbBBM#y%o+GTLvNC`jmej#jZl+lKH;EuQX$&C58V-T?S`s4? z12ykJ}=0EO#1hdD&t1g!JCp@zYYLV;uR*$lJaG@4ck8H((9Mgri}HcSLL`)u=MHi zu87By3&h3X6Lt2#!&fe?qhq3HK%iw(GxRRQ3jD@GU_(~;MLyj`P4iX|z-KT#|CAjSVGGBo9OyUuPZh*`ZLmkC_Riur3cHYU|ye|(AA%_%w#mK|;Em+2*Q?%jL10=Rh`SaY~$ z&(jzVd+IHBTbqu>?xY_!@`nj1MQFd@D?VrvXZBg?yd>LX;iPT_Fy`kORTk{pGFo=k zjeM`Eh{@ur6Z(IO=+1}eNPa}yOE+p_hBphJVM#W<6! zBo`s!^4vM@&iGZ3GRPmL9xmZ~Kqu2OO4cZJ204&kf?a;9#DgV?haw#ui*F0B*JCb zT3k3U&3o;2?1-1B%8S>UD{6!6sYTBd7XF#XO6A8j+Yg>VrqT^Ws^7ILxr#vWO2%E= z#uBFYd_K*uWlRUpPc;cIp2rZaYAvVmqTy$smFU&!i+&B&%r+A@tIvZJx3bf#c;p9atZ#m>=Jf>UUme9RF45OMAI+c zuHGq8+^-61e-cE^QmeeZHuFEAK&e}Ypg<+hG=Uxgqy#qn!<85C~3(mTB1y*1D}^9j(FPM8OQ1+lB@cm#R;0Js@Sc4}nS(>-MOLq#6t$xgaOF$@a=YDe)vRJ|51NvmKqbEt!7t45U~0V#7d+o_z`R>S-UaD82boE;eGvT$H5s&zOz1=QMdn`lsCkf`oZCA zEqXjnzN`4Z?HBK>?xVj^o_KWS*^D={F1 zI%>W9USAG)JDEx>9sye*?C|+VF~CfbFsMz96c9@3y8uk~18p95hkTlPEe=aQO! zB4&&xNMJU*`OwwLjr{Fd?`57rDAJ?c>mi{Z8#qztrJvKEUe%TT&N_ZdTH%^5z{q)s zorT6d87*L7HP8DjXau`*Ve*i_Aa$nWEGLONf{cb8%mo9py6cxa1u>MV2Y4F9#N|ZO zM2}LBJnx5 zt!kE3;*eB%2P)&8s8UBSY#<5(Z%1)k#9Qw5Y zyJ)S8iKFf;EF47)KA-fmHjuOa&@N3z>gxf;U?tcQ?&@1* zHtjk)Mdnaxp5qmb?KzK?4p3SD_G}d?xw@X9jHQC{PUsoE_%bH2MMfIvQFj3DqAtqw z!6Q9qRO+=)BN3*B71t#iKytmU8GQ)Jz8y{=fb2t+dH)UmM0_fMKpr7KYXL-#-t5LX z=P+y%6zr+!$k1enwH~a~5_kO$2!f4e2sxmIQBcKr70#^-;DR~nyWe(dDyAO8Ru6pH-k+SP zeXpc*&5ItRawC?U>8mBvKQ|+P3^7pz-qA4ne8ns*zd)yY{6Vgzy`Q_IqvK%Y(nzK~ zV=m8ld22zLu8H22@v0z-nHmc7Z*6>wcJHnAnG<!^|Ta>oPRZmjo@HnUP=b8Vx zjg!8@J890?{!-(jdo%Icx`%l~y^JV5e5R4D=+7{*j&EI)!i0YiiT0`I?JZWYvpqUn zsh06dQ@*}PQ@5M78}8=xnI7K2pne0)heHj8N08Z41{MRZM+gFGXw9L zdu1*F{~>B7z(tH!GI2t7j0}zyyDbvB_SZ$*%nTQ9+H&W-)D&GYz1bBH-}~m=CAl-l z>Io-wF07g73f-FdG`ZYf$_KV{u&jcpClUF* zh8jt8X3S0rPbB0$-~#k^9BBmIJ}L`ZCyNUOd$*e#dr3Bwi8|Alh|c*lxBRVR@gQg0 zs=gLu=>Yk<2zkt;Rr&$ozsKFK!>w%`(=h_ixi|VX0_B%FJLH;OKDOUTi!bFP7cX(; zTt|Ot#~u@{@|oI}Q|xktefwiG>=nntN426))s4;!Z8h>)f{W*C*+*(u^ZWq~(D;o& zs_qj7bV9cfsKOO}R@x!gGr0cg^~^H}lpIZ>er05ne@amVy!$Cz|F@*t)oR>6wpGfGIqL)r5Eom>(MVCr-l}sBtd5w?bh;!SSRQh#`%6UCjEF7Q$tCNAt&n94vMZ8D_3xs zHCHdm<5T$7CV)H|{Az-n;TEgt=?uCLvucUvo>7MZ$qJ|auc8?C30{%)$jJRy zc76Dw9aDp%rY+l97;Yy0@|^B@;(VaY<>Qt1D}HWuz7-x4a$5K}v7)YagckS3CR}r` z3{N3_o&fhAsjAdA%}i7JsdR)p%qJvvL4H^7=9 zS+gdMzyBmvH#L=|EdE9LCXcI7 z;N2Nm4mPefGylpJX?&Ww@%>#E_n_*)m`fOM?#Y`Y6Ciz##8kTXXH<>MH}Gn;sHTXg zrLSl|`cS;Z799w-r4Oe)l`W8!Jtc+d8uxCk^J^PO^Hey22ChI!;3?lH8Cn#+D%Tn~ zH=)5)1iW9WIx2pHD&5;*rz~nLesaG0V=4pfxEV%1LB0pCU7mVi{=_*2@!A-;ea>JG zwj8bLzxe0CnGTLF)SWgTGx#_5n#{)M)wJa`Rm=+#29kAM6TBXLOYT0jTLt;Nb1<;^ zwlvPPpfGE&n_Es!p#+zd+|_@=2t{13qon3^GlMep4aeyr@uk zD}P}^s$^(rna11`{gCMc9l)ttawR-+RWP@A&CkH!hh!_K)7|G_v9$x&p~*>X=&!UOsVoyw6?Sl>}wwXteOwp<)P$)lvHa4RgbASkEPqwcH$9!JR|_h)LQIC5KHIMJ@h3sgnZ?MTT+Z~`4pF*cELHig zRDq|oi<@DyS#On4D|!4a^!QA|Q#hyk#w#Rr9?V#0IC7Xv0XuXlC+PY*dY~)Zq1Nh& zcD}7dlQS0V@3llsE-o;y}UoXq}eG%9lbr| zv1^g$`H8*wxLJ2~@o~h$u&8OxmnoI!=g24Z*GDEOaLqv#m2=KXwcrfTk( zl{n+VyBr~9FdHT0f<_*fM-v=c%HNb&e7}Wx4oFgEKrbMADQGa$f33 z_-7(IpJ+IG_w=f=UH6B88Y)tmzbx+^Gl+-$!_n|5)!v2nzHU>zlxq&dNFye|6PkS)`JuDiBM}<$rG$~JuE8Gm3 zEmDI|80_J`yr!z2+?TB}5$bFVQ?*i~=co2toXdL4uc3wXcfV~bLY6wU8%+U4p585|!yPt7~Y zI(Rm6<;CE1m%YOy#vJtA7EKGvLe?A^I8T5|`wX{3KP3@wvkBO}ogmre2}wj$zVYy- zE6=TklbnGqsk53SnF-HHWp|V-)5M44U4D;ncc-35)cd(&R-N&0lKSu;R8jEFdS5`; z-BH{j%z)yGt$hv2$XO36+h=_==)SSd6_y}HC_zoT7TsM#G}Uz5Ni6L9&QV|)ccORm z^|4R_(L#nf!7+hL98=v7ndbEP&u9S3lTG`~%2`o}N6AM6Ojy@y$-m_#u)ABAfmY+> zq*81&jy>|`iemY*&1?altIj92^L23 zTi%drv+8jpLk?*?&gg$8l1MDN94I_>%KL}2f;pd3QTUQSTD!J^4V9W4U&m(}2t z#w1QAY|!b+?Ph*+ZS9Chz*(vB`1JP2XEz0okGD@l#IklQ8pTHnq#_-N>BrZ-@9X-a z(t`GJDPFHHQF>MzmO89%<5D@}E;<_!d$g#UvXwrxyiNvfxpv zftO>zoiLKop*oe?)d?S;epxSu6xosR^Edmc8^zi6u;@Pim(bVju<_eV4ZUP+0_VoU zu`FmONu?#O-sN5y_e%%N0kQ?GNLLo*t++GWqc)nG8P0!!duZ*1zFj8D!uir z5)_1Emxc3VuA?q_N|hf{KfMFF0;^`Y-D-ifKhn9^2!p^`L(wg52YE4B`lB2~^RH~U z#SyriOaLT-LhwKH;yvI1y)wW>IJ@L~k1&zXC7Lv1^<(|DBULT8+1pn{8)AfcA}}rLq->-@OR|Tf>h3`xVEn2LD3o1XBa;VpOM5k9(6QOb|3t zICx3`cg!fh@9kPc*EP*<&mUxwgRK6>YhjaBgB(1IeiKikEvb%y)^NQ@ks5P%X{sAv zT?wwbTY`^sp0NCSi|u$aZCDzduh;r8RmDU%;{p-h!`atnHsaV;;5l&ZnRie%_q-d+ z6C1=%56!0|i5;jUYWToS}R6S83TX+|p(d0&Yzio541mXGlC7gW#B!TLRf1zc%G{I}oD z$L3%CM%d9izn*G$F?V=Utmcv0tKo{B!M3qQpe%OXZalGT!)_tDiNqZ>6+}d$u{$bGjhpstkf%?P=S8QD82dwGyTtg$A&k_^8>!$ZLrP>917_NtwW(j zIy`q#B0L86tJ;}Y834N=$5+k0b&9gfV1L}+xx-XF6WwMoJf}3zbF+_jZ69)sa3aa? z+$N)9DvgOi3^Yh-%YJYAt&4{NP<9D?(Wks$#3j`eV}#L|RW_2XBu+-)rC& zBX3)mg(#PPagwfW`k)8l-W?}Tgg_5&BOA`Po`D^*(hMN-T%fn&L+7^mWe)i-WVxmJ zfgenUBXC+K%emF)|(h{4TeCZ8S~_<2+b2rF72WuvG39 zVSW~`z2ACkhLtOVL5$li8`%8#Z=H0H>Xjo0DQ$51#7%*%fwOZkEbkc#&b1o9_k^YL z0_UT7=Qu}Z*DM2gUd?wB_0p%{{A8Aebb(o07=cW#IuvJp1lu1tbvs&mdNQS*WB z(BqA_2p*Ff4L|g_%ltcb-=w%l6>M3fnR{y5eJd%jbHi4*k)A9OUA=gH&ZG& zSa7N>?s{KGUcNeBdK(vC;5W8Ed)ha^t$r zNks8BVNuY8w67ybfp9B=MuUC4_S&0&RMw8{_%yuP31O6WX8ZxE=ILeoC6ZL=IXao$ zv5QoG11GFwayER15jK$Oe?%mP-R9@Eyi+;b9^riIlQM&fv2Jf!t^|Eh)kPs!92M+O6 ztjL^#TpCl!+j;mM3}1q3&j=#NkA?H!_kas*v-h*{+f5ChC>TZkios1uhZ7je;AB|g z5^o&S32z0V>yKBLW`EMv1+zWFaXi`dHA@+??X4(>^Uc3?hm@-|O;z@e^5I}PaRFtpZbOy)K;OtQb!b|iA=2Jy3fKuH%W=*vuF!y z)Zidys|F=9z3gCX6YyaQ1M5Y0yQnPf@cd$Egi~haejksyp|wI9kN(?b7vcnE+9sma z-l22j)X#on0n@-0Txt1$$nooyGQk3k-tO`yLm_*oueqY(FX}?+DmO^x4W&rdetg_2 zs33jqcUnXk1gNH2U59*kvLXwefcSErevUq+is0%+E&xod>jB+(9H!2{!@n+1({-U8R2@5i$c154I>nNU1F@p38jebkLbq zv4nYnpEm=_71TmiQo1SIUGm6}iS>sJqGVY|EIUkKRvkVl>ep(?f3eSTl-M_ivdo~w z$HB()^>8#pTkmnTMtV-|8%2lrx+A7x>PO~%UI8+5G?Am!EC0KBlkc0lhD_1|Hm-Je z`fHZQ8NJFEzX&m5z=ZWWV+M>29n9no=|{+-+(`52U(>?uh6V^R^ljq~7_dPrWF>&j z)iKfQ)!5erLE*FKj1Z?Ci?E+7P)y|D+svbvo-P2ZmB*9(I_{!QpJwgNer6chbe?{q z(#fbV|EPB|U|U_N8>W6L`0P7d>#V+XqCBRHra$RW$fDFiy+BU&W@~y+I+!_(jMB{h z8(wF~BN+y)@0qPt`EPVfdQ>Wv1+upXo;J7fmq7{as1WE!?_yu@TbyW zTTsozp*EK%O264OyapEL8-)L%tWIU)?)RUghDi@l5vV9yQl!}xVzjd~&nw|XoD6FQ zYL7UJnNq6|5Vo@zmyK6nXTl?Xh9!Fz{6i!sd$&gk*7-~E^*#d^1sER`WSl)9FOuyc zJvrcygE6N}OEP+YAM#VH9ISzzIOu97TZ0$ScwQnOA@XY~`+}^J`*a-XnNV^O`19U1QVSw3RI1IC!RPPx~}1 zg75qJQ)L@Avz8Ryx_sF*T&COm9=E6~dSj0zF_^;rvI;7A8RRLt0ImL&@S8h3smGB? z_cQBwW3qQjuNld)=pg3VjwARxIeCg6jQ#0-Y{wVl;x;kVjhE+YjC53rc-UvS z^FrmH2bMf00fdw+n0o8}!L790!s%64^EQ42WIdxcL!uY+xvESJDv9;+=gRIq$!Y!E zD%ulUHm~Qm6PXWVgUUpUzQXv05dOURF1!3Y(bv+)BViWo>*Otveqdc?d(>uO{~)C= z8kddqMbG#M7Sb^x1=7Kz%|x!VPQ!*Z&SXvNMYeNso!Rq;`8;}|(T~CpNl@XHYAV?_ zntT?$V0mZW6W;r!wJnT9(4VWr`MBUY*FL!Il}%vkhB3`p)$n>gW&V=X;?5A z9>F9j4M&B8h)&gYovr-u4yxE&&`aSD2&D96@MU|G$!h*~y z^MCBv&n*5RJ}nP?{<@6rL9OCkb=~$IlfUaeaY`z626Y$BebE_Hluo zGL7AB=tq?^b!EIizTt6H)9V9`Z5H8|RtumrO73zrQPEwdjSi6b@*GcPA>_i%&3UNn z!Q8*jWB!dG!9dkV+#B6PdF7U@?xZ^!tZz{25?**UnT0DJlXc`N9^pegh);M@F+iT? zug*=4*_`lxX3QRvRqYTMA`;-k>ov_cgz)h5-zVmWy}0dI_=%I$(ayN~eN%3Alj)(l zhZRG0v|O*D2z#*XKq<~lp!-v1FH`L5+NTegO2w3$Tk6jvS7A*z9?ezWPgsggAHiF3 zsS1KL7&AITBt4!1?j0hQ87(rsIoRmUpeu*sUkJ)D_^U*+Kkjo8n@_FBTIZdD;;QX> z%lYwuB*njp_$=6&YemX#s@Qz9;z6r5^T?sDmc;j}a6<^5&U=u0g5T(AJV(`HDoWy$ zjGC2kV9}PLYwTSPrC52t z5@=~{e%(7sTB-+u-g|_`$++qa!1T!_*V8*`<8*g(Tr5`_;qw|X5xIQo@fy5U(?D_m z$F=TGy>B(`x(QYgOPrjmP=9;c?G`^~+bi`;>cVX94lSwK8LN2(+PXcA#z~^DK%Y+t z>{!d*cEB-M2rPxs7KuwFiL3i^hPup(Nwx)q!*LSosc1Ooo(}S(w+2_P^a1%GlkI#b zO2M}u<{7=ik62e&ibJj}U6VA3i@)xt`UVdJ7SV-tD42nQ!IaqT0Pk-HA`?eHF9BWbR!^VcP zh;`bfoNaElcFs%B+!)gGoY?3Je0MyQanE`U;amH2j?L3eQYS9Y)H{n?O-9AEp(JjH z8H=?*=;HJ-9$yi;>ar_%$WpihgUSn`Kzv;F$>I~&sG?`VhIR2&9c!ZtF zv)&rL72?Eu2T^@MMkXI};c9yshIwP4s{E6@>wSyatk>}^Gq5}Lie-4pra8T{x?Nwj z{3x}ZgIpT^*rKC!td(TQ7;lW}LEOPrYOwlgX&NMTt;SaQGkc7kw2rh?tx~51HBr>e z9+Lfi^FuZDN;zT%}x)u`E9sQ8K~<5>Kh3sn`n! z)}1d{O3apb-|siS!L39c+F`?;b#(S(bCHZmJL>Y*u7(}wWI{D&IR2}8%&dh(5?hBd zB>wX*YyqzU)@jDrdmStl&rw>k49qLWZv`YV%e4u+O=Uh4|XIDAEsDN zU7Z{Axs7iaCsUHEba7ZT-7qBQ;>_l|NrMBi(pMGtnDD_{@oC#vgMB0BcCHXTh% zG7?)++SboYBI_FWhq}h)?cNO+$9`?-f2fcEAq*j7jOHL8cwsqWPd+4zCb8OS>evz;b8WO!HS5Y$DC7Nm& z)yF-rTdJmT&9UtT#tL(;A-UqOG_&dNS{6AcLCP@ioetPUSGCLg&>{7CNs1VxpD*xF zGu*bjzH+Pcpk(dPIc{;0dfXcU&$0gL?2){ws_Ab`>ravF>>(9NMsT;eN+t4G=%(6# zylB}l^AjwaDgS^X5$NX4f1g)NWvR{F{pa(5WBp7ny3Kq=s$y?SWFpOeC@s|OTo z0U3?$B;y4>0YXPaqyNt0Txvs+7Q&N8PO(QKy_PH5CQPdNd&v(o$N?#+Wav3`4%}zv z!+LA|dJ|asuUegz6!iOX_{?VSeTqwQdrxP#|~jvFX~DCpf(9qpk0*tEdH2fvS^{d5sA6m5`DB!AbVg z9i01{bk60zwWW@h8aQNOIXc$1b$77*b;jbpyQRW?Z#Qs4NLWBrgme|@9L&<(*~x-* VT2N3}MC7zztURU?{P3^0|6g1Asign_ literal 31488 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 Date: Sun, 17 Dec 2023 06:46:12 +0000 Subject: [PATCH 42/53] fix feature #199 --- .php-cs-fixer.release.php | 18 +++ composer.json | 7 ++ docs/assets/pre-push-hook.png | Bin 0 -> 80907 bytes docs/contributing.md | 9 ++ .../src/ApplicationVersionFixer.php | 118 ++++++++++++++++++ 5 files changed, 152 insertions(+) create mode 100644 .php-cs-fixer.release.php create mode 100644 docs/assets/pre-push-hook.png create mode 100644 vendor-bin/php-cs-fixer/src/ApplicationVersionFixer.php diff --git a/.php-cs-fixer.release.php b/.php-cs-fixer.release.php new file mode 100644 index 00000000..e0743c18 --- /dev/null +++ b/.php-cs-fixer.release.php @@ -0,0 +1,18 @@ +registerCustomFixers([ + new ApplicationVersionFixer(), + ]) + ->setRules([ + ApplicationVersionFixer::name() => true, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in([__DIR__.'/src/Console']) + ) +; diff --git a/composer.json b/composer.json index 1d6d4e41..0c277959 100644 --- a/composer.json +++ b/composer.json @@ -54,6 +54,9 @@ "pre-commit": [ "composer style:fix", "composer code:check" + ], + "pre-push": [ + "composer qa:check" ] }, "branch-alias": { @@ -73,6 +76,8 @@ "@composer bin all install --ansi" ], "cghooks": "vendor/bin/cghooks", + "qa:check": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.release.php --using-cache=no --verbose --ansi --diff --dry-run", + "qa:fix": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.release.php --using-cache=no --verbose --ansi", "style:check": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi --diff --dry-run", "style:fix": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --using-cache=no --verbose --ansi", "tests:unit": "vendor/bin/phpunit --testsuite=cache,configuration,finder", @@ -84,6 +89,8 @@ "minimum-stability": "dev", "prefer-stable": true, "scripts-descriptions": { + "qa:check": "Run QA style checks before pushing new tag and releasing a new version (only dry run - no fixing!).", + "qa:fix": "Run QA style checks and fix violations.", "style:check": "Run style checks (only dry run - no fixing!).", "style:fix": "Run style checks and fix violations.", "tests:unit": "Run unit tests on following components: cache, configuration, finder", diff --git a/docs/assets/pre-push-hook.png b/docs/assets/pre-push-hook.png new file mode 100644 index 0000000000000000000000000000000000000000..34e247064a47ddbcb7b0a6bb0f4176053cd50dd4 GIT binary patch literal 80907 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 acD5$RxB2)4c=#{+SoO8EffxTf`u_`SKt%2U literal 0 HcmV?d00001 diff --git a/docs/contributing.md b/docs/contributing.md index ea776ef7..26e9360a 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -6,6 +6,15 @@ We look forward to your contributions! Here are some examples how you can contri - Suggest a new feature - Send a pull request +**WARNING** : Since version 9.1.0, PHPLint enforce QA by verify if the Application Version is the last one identified +into the `src/Console/Application.php` file. +This verification is only executed when you try to push code to the remote repository. +To avoid such check, use the `git push --no-verify` syntax. + +This check is only for maintainers of this project to prepare a new release and forgot to bump `Application::VERSION`. + +![pre-push git hook](../assets/pre-push-hook.png) + ## Workflow for Pull Requests 1. Fork the repository. diff --git a/vendor-bin/php-cs-fixer/src/ApplicationVersionFixer.php b/vendor-bin/php-cs-fixer/src/ApplicationVersionFixer.php new file mode 100644 index 00000000..3b1bf0a7 --- /dev/null +++ b/vendor-bin/php-cs-fixer/src/ApplicationVersionFixer.php @@ -0,0 +1,118 @@ +isAllTokenKindsFound([T_CLASS, T_CONSTANT_ENCAPSED_STRING]); + } + + /** + * @inheritDoc + */ + public function isRisky(): bool + { + return false; + } + + protected function applyFix(SplFileInfo $file, Tokens $tokens): void + { + foreach ($tokens as $index => $token) { + if (!$token->isGivenKind(T_CONSTANT_ENCAPSED_STRING)) { + continue; + } + if (!$this->isVersionConst($tokens, $index)) { + continue; + } + + $tag = @exec('git describe --tags --abbrev=0 2>&1'); + + if ($token->getContent() !== $tag) { + $tokens[$index] = new Token([$token->getId(), "'$tag'"]); + } + } + } + + private function isVersionConst(Tokens $tokens, int $index): bool + { + $prevTokenIndex = $tokens->getPrevMeaningfulToken($index); + if (!$tokens[$prevTokenIndex]->equals('=')) { + return false; + } + + $constantNamePosition = $tokens->getPrevMeaningfulToken($prevTokenIndex); + return $tokens[$constantNamePosition]->equals([T_STRING, 'VERSION']); + } + + /** + * @inheritDoc + */ + public function getDefinition(): FixerDefinitionInterface + { + return new FixerDefinition( + 'Application::VERSION constant value must match the current git tag.', + [] + ); + } + + /** + * @inheritDoc + */ + public function getName(): string + { + return self::name(); + } + + public static function name(): string + { + return 'OvertrueCsFixer/application_version'; + } + + /** + * @inheritDoc + */ + public function supports(SplFileInfo $file): bool + { + return $file->getBasename() === 'Application.php'; + } + + /** + * @inheritDoc + */ + public function getPriority(): int + { + return 0; + } +} From 02227bb6b8d5e30e08692f0aed03591b2b5757bb Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 17 Dec 2023 06:54:44 +0000 Subject: [PATCH 43/53] minor updates on GitHub Action example into documentation --- docs/usage/github-actions.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/usage/github-actions.md b/docs/usage/github-actions.md index 64eee25b..f280c3bc 100644 --- a/docs/usage/github-actions.md +++ b/docs/usage/github-actions.md @@ -7,7 +7,7 @@ Quick start, if your PHP runtime set up is not important for you. ```yaml jobs: php-lint: - name: Linting with overtrue/phplint + name: "PHPLint v9" runs-on: ubuntu-latest @@ -22,12 +22,12 @@ jobs: ## Use case 2 -Otherwise, if you want to detect specific PHP features used by scripts depending of your PHP runtime, then use this case. +Otherwise, if you want to detect specific PHP features used by scripts depending on your PHP runtime, then use this case. ```yaml jobs: php-lint: - name: "Linting with overtrue/phplint" + name: "PHPLint v9" runs-on: "${{ matrix.operating-system }}" @@ -42,13 +42,14 @@ jobs: php-version: - "8.1" - "8.2" + - "8.3" steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - repository: overtrue/phplint + repository: sebastianbergmann/phpunit - name: Setup PHP runtime uses: shivammathur/setup-php@v2 @@ -60,7 +61,7 @@ jobs: run: | curl -Ls https://github.com/overtrue/phplint/releases/latest/download/phplint.phar -o /usr/local/bin/phplint chmod +x /usr/local/bin/phplint - /usr/local/bin/phplint -vvv --no-cache + /usr/local/bin/phplint --no-cache --no-progress -v ``` Follows steps: From 6537f697a8e181fd529bdd8f0fdaaa3fe899bea7 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 17 Dec 2023 07:05:41 +0000 Subject: [PATCH 44/53] prepare feature release 9.1.0 --- .changes/9.1.0.md | 27 +++++++++++++++++++ .../unreleased/Added-20231208-064223.yaml | 3 --- .../unreleased/Added-20231213-092438.yaml | 3 --- .../unreleased/Added-20231213-092714.yaml | 3 --- .../unreleased/Added-20231214-055634.yaml | 3 --- .../unreleased/Changed-20231202-094819.yaml | 4 --- .../unreleased/Changed-20231207-092611.yaml | 4 --- .../unreleased/Changed-20231208-061436.yaml | 4 --- .../unreleased/Changed-20231209-153537.yaml | 3 --- .../unreleased/Changed-20231216-064904.yaml | 3 --- .../unreleased/Changed-20231216-065543.yaml | 3 --- .../unreleased/Removed-20231202-085629.yaml | 3 --- .../unreleased/Removed-20231202-090942.yaml | 3 --- .../unreleased/Removed-20231214-095810.yaml | 4 --- CHANGELOG.md | 27 +++++++++++++++++++ src/Console/Application.php | 2 +- 16 files changed, 55 insertions(+), 44 deletions(-) create mode 100644 .changes/9.1.0.md delete mode 100644 .changes/unreleased/Added-20231208-064223.yaml delete mode 100644 .changes/unreleased/Added-20231213-092438.yaml delete mode 100644 .changes/unreleased/Added-20231213-092714.yaml delete mode 100644 .changes/unreleased/Added-20231214-055634.yaml delete mode 100644 .changes/unreleased/Changed-20231202-094819.yaml delete mode 100644 .changes/unreleased/Changed-20231207-092611.yaml delete mode 100644 .changes/unreleased/Changed-20231208-061436.yaml delete mode 100644 .changes/unreleased/Changed-20231209-153537.yaml delete mode 100644 .changes/unreleased/Changed-20231216-064904.yaml delete mode 100644 .changes/unreleased/Changed-20231216-065543.yaml delete mode 100644 .changes/unreleased/Removed-20231202-085629.yaml delete mode 100644 .changes/unreleased/Removed-20231202-090942.yaml delete mode 100644 .changes/unreleased/Removed-20231214-095810.yaml diff --git a/.changes/9.1.0.md b/.changes/9.1.0.md new file mode 100644 index 00000000..eb6f8969 --- /dev/null +++ b/.changes/9.1.0.md @@ -0,0 +1,27 @@ + +## 9.1.0 - 2023-12-17 + +### Added + +- PHPStan dev tool to enforce code quality of this project (see Contributor guide) +- Introduces a `DebugFormatterHelper` for asynchronous process +- Introduces a `ProcessHelper` for asynchronous process +- Introduces a new extension (`ProgressIndicator`) to let users know that the `phplint` command isn't stalled. Uses `--progress=indicator`. + +### Changed + +- Replaces Symfony components constraint to new LTS (6.4), and drop support to old one (5.4) +- `ProgressPrinter` and `ProgressBar` extensions must now implement the `Overtrue\PHPLint\Output\ConsoleOutputInterface` specification +- Reorganize dev tools under their own composer namespace (`check-style` begins `style:check`, and `fix-style` begins `style:fix`) +- [#197](https://github.com/overtrue/phplint/issues/197) : Faster process linter +- rename BOX config file to `box.json.dist` +- Dockerfile bump default PHP version from 8.2 to 8.3 (to produce better perf) + +### Removed + +- drop support of PHPUnit 9 +- drop support of PHP 8.0 +- `setApplicationVersion` and `setConfigResolver` methods were removed from `Overtrue\PHPLint\Output\ConsoleOutputInterface` +as there are no more required + +**Full Changelog**: [9.0.6...9.1.0](https://github.com/overtrue/phplint/compare/9.0.6...9.1.0) diff --git a/.changes/unreleased/Added-20231208-064223.yaml b/.changes/unreleased/Added-20231208-064223.yaml deleted file mode 100644 index ffc8cfc5..00000000 --- a/.changes/unreleased/Added-20231208-064223.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: PHPStan dev tool to enforce code quality of this project (see Contributor guide) -time: 2023-12-08T06:42:23.834383742Z diff --git a/.changes/unreleased/Added-20231213-092438.yaml b/.changes/unreleased/Added-20231213-092438.yaml deleted file mode 100644 index 089241ff..00000000 --- a/.changes/unreleased/Added-20231213-092438.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: Introduces a DebugFormatterHelper for asynchronous process -time: 2023-12-13T09:24:38.33870516Z diff --git a/.changes/unreleased/Added-20231213-092714.yaml b/.changes/unreleased/Added-20231213-092714.yaml deleted file mode 100644 index 8e76a350..00000000 --- a/.changes/unreleased/Added-20231213-092714.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: Introduces a ProcessHelper for asynchronous process -time: 2023-12-13T09:27:14.222002659Z diff --git a/.changes/unreleased/Added-20231214-055634.yaml b/.changes/unreleased/Added-20231214-055634.yaml deleted file mode 100644 index 6c8994f6..00000000 --- a/.changes/unreleased/Added-20231214-055634.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Added -body: Introduces a new extension to show progression (ProgressIndicator). Uses --progress=indicator -time: 2023-12-14T05:56:34.231275002Z diff --git a/.changes/unreleased/Changed-20231202-094819.yaml b/.changes/unreleased/Changed-20231202-094819.yaml deleted file mode 100644 index ea2d7381..00000000 --- a/.changes/unreleased/Changed-20231202-094819.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Changed -body: Replaces Symfony components constraint to new LTS (6.4), and drop support to - old one (5.4) -time: 2023-12-02T09:48:19.920649128Z diff --git a/.changes/unreleased/Changed-20231207-092611.yaml b/.changes/unreleased/Changed-20231207-092611.yaml deleted file mode 100644 index c56d6f17..00000000 --- a/.changes/unreleased/Changed-20231207-092611.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Changed -body: ProgressPrinter and ProgressBar extensions must now implement the Overtrue\PHPLint\Output\ConsoleOutputInterface - specification -time: 2023-12-07T09:26:11.396636982Z diff --git a/.changes/unreleased/Changed-20231208-061436.yaml b/.changes/unreleased/Changed-20231208-061436.yaml deleted file mode 100644 index 13dfd1a4..00000000 --- a/.changes/unreleased/Changed-20231208-061436.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Changed -body: Reorganize dev tools under their own composer namespace (check-style begins - style:check, and fix-style begins style:fix) -time: 2023-12-08T06:14:36.107069306Z diff --git a/.changes/unreleased/Changed-20231209-153537.yaml b/.changes/unreleased/Changed-20231209-153537.yaml deleted file mode 100644 index 7ccff574..00000000 --- a/.changes/unreleased/Changed-20231209-153537.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Changed -body: '[#197](https://github.com/overtrue/phplint/issues/197) : Faster process linter' -time: 2023-12-09T15:35:37.334855942Z diff --git a/.changes/unreleased/Changed-20231216-064904.yaml b/.changes/unreleased/Changed-20231216-064904.yaml deleted file mode 100644 index a7d23e9e..00000000 --- a/.changes/unreleased/Changed-20231216-064904.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Changed -body: rename BOX config file to box.json.dist -time: 2023-12-16T06:49:04.321497121Z diff --git a/.changes/unreleased/Changed-20231216-065543.yaml b/.changes/unreleased/Changed-20231216-065543.yaml deleted file mode 100644 index 981ffc16..00000000 --- a/.changes/unreleased/Changed-20231216-065543.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Changed -body: Dockerfile bump default PHP version from 8.2 to 8.3 (to produce better perf) -time: 2023-12-16T06:55:43.932148624Z diff --git a/.changes/unreleased/Removed-20231202-085629.yaml b/.changes/unreleased/Removed-20231202-085629.yaml deleted file mode 100644 index 62790487..00000000 --- a/.changes/unreleased/Removed-20231202-085629.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Removed -body: drop support of PHPUnit 9 -time: 2023-12-02T08:56:29.094840086Z diff --git a/.changes/unreleased/Removed-20231202-090942.yaml b/.changes/unreleased/Removed-20231202-090942.yaml deleted file mode 100644 index 5a1fa0c8..00000000 --- a/.changes/unreleased/Removed-20231202-090942.yaml +++ /dev/null @@ -1,3 +0,0 @@ -kind: Removed -body: drop support of PHP 8.0 -time: 2023-12-02T09:09:42.469702494Z diff --git a/.changes/unreleased/Removed-20231214-095810.yaml b/.changes/unreleased/Removed-20231214-095810.yaml deleted file mode 100644 index d96a0075..00000000 --- a/.changes/unreleased/Removed-20231214-095810.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Removed -body: '"setApplicationVersion" and "setConfigResolver" methods were removed from "Overtrue\PHPLint\Output\ConsoleOutputInterface" - as there are no more required' -time: 2023-12-14T09:58:10.457061253Z diff --git a/CHANGELOG.md b/CHANGELOG.md index f900fed4..262d599a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## 9.1.0 - 2023-12-17 + +### Added + +- PHPStan dev tool to enforce code quality of this project (see Contributor guide) +- Introduces a `DebugFormatterHelper` for asynchronous process +- Introduces a `ProcessHelper` for asynchronous process +- Introduces a new extension (`ProgressIndicator`) to let users know that the `phplint` command isn't stalled. Uses `--progress=indicator`. + +### Changed + +- Replaces Symfony components constraint to new LTS (6.4), and drop support to old one (5.4) +- `ProgressPrinter` and `ProgressBar` extensions must now implement the `Overtrue\PHPLint\Output\ConsoleOutputInterface` specification +- Reorganize dev tools under their own composer namespace (`check-style` begins `style:check`, and `fix-style` begins `style:fix`) +- [#197](https://github.com/overtrue/phplint/issues/197) : Faster process linter +- rename BOX config file to `box.json.dist` +- Dockerfile bump default PHP version from 8.2 to 8.3 (to produce better perf) + +### Removed + +- drop support of PHPUnit 9 +- drop support of PHP 8.0 +- `setApplicationVersion` and `setConfigResolver` methods were removed from `Overtrue\PHPLint\Output\ConsoleOutputInterface` +as there are no more required + +**Full Changelog**: [9.0.6...9.1.0](https://github.com/overtrue/phplint/compare/9.0.6...9.1.0) + ## 9.0.6 - 2023-12-02 ### Fixed diff --git a/src/Console/Application.php b/src/Console/Application.php index 07ba9ae6..88c09c17 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -36,7 +36,7 @@ final class Application extends BaseApplication { public const NAME = 'phplint'; - public const VERSION = '9.1.0-dev'; + public const VERSION = '9.1.0'; public function __construct() { From 88aac193c179b0996553f61908535f08432f8c50 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 17 Dec 2023 07:15:10 +0000 Subject: [PATCH 45/53] fix change on Docker image with recent release 9.1.0 --- docs/usage/docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage/docker.md b/docs/usage/docker.md index 21746c51..39d6ae4e 100644 --- a/docs/usage/docker.md +++ b/docs/usage/docker.md @@ -6,4 +6,4 @@ docker run --rm -t -v "${PWD}":/workdir overtrue/phplint:latest ./ --exclude=ven > Please mount your source code to `/workdir` in the container. -**IMPORTANT** : Docker image with `latest` tag use the PHP 8.2 runtime ! +**IMPORTANT** : Docker image with `latest` tag use the PHP 8.3 runtime ! From fb49b61fa4da5effac3ddb4b0b153d96107d763f Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 17 Dec 2023 07:25:36 +0000 Subject: [PATCH 46/53] fix path to screenshot about qa:check script --- docs/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.md b/docs/contributing.md index 26e9360a..9fe83149 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -13,7 +13,7 @@ To avoid such check, use the `git push --no-verify` syntax. This check is only for maintainers of this project to prepare a new release and forgot to bump `Application::VERSION`. -![pre-push git hook](../assets/pre-push-hook.png) +![pre-push git hook](./assets/pre-push-hook.png) ## Workflow for Pull Requests From 78bbe24d4d00a96b4d2ac2ea49d31c451219ae19 Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Sun, 17 Dec 2023 15:11:22 +0000 Subject: [PATCH 47/53] extract autoloader procedure to be reuse later --- bin/phplint | 22 ++----------------- config/bootstrap.php | 51 ++++++++++++++++++++++++++++++++++++++++++++ phpunit.xml | 2 +- 3 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 config/bootstrap.php diff --git a/bin/phplint b/bin/phplint index 5c8c778e..b17c66a7 100755 --- a/bin/phplint +++ b/bin/phplint @@ -1,27 +1,9 @@ #!/usr/bin/env php getAlias(), + ]; +} else { + $possibleAutoloadPaths = [ + // local dev repository + dirname(__DIR__), + // dependency + dirname(__DIR__, 4), + ]; +} + +$isAutoloadFound = false; +foreach ($possibleAutoloadPaths as $possibleAutoloadPath) { + if (file_exists($possibleAutoloadPath . DIRECTORY_SEPARATOR . $autoloader)) { + require_once $possibleAutoloadPath . DIRECTORY_SEPARATOR . $autoloader; + $isAutoloadFound = true; + $vendorDir = $possibleAutoloadPath . DIRECTORY_SEPARATOR . dirname($autoloader); + break; + } +} + +if ($isAutoloadFound === false) { + throw new RuntimeException( + sprintf( + 'Unable to find "%s" in "%s" paths.', + $autoloader, + implode('", "', $possibleAutoloadPaths) + ) + ); +} diff --git a/phpunit.xml b/phpunit.xml index 5c371142..8f1685b2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ Date: Sun, 17 Dec 2023 15:13:35 +0000 Subject: [PATCH 48/53] introduces resources/build.php script to automate UML graphs generation --- resources/gh-pages-hook.sh | 16 ++++ resources/graph-uml.phar | Bin 0 -> 106566 bytes resources/graph-uml/build.php | 88 +++++++++++++++++++ resources/graph-uml/callback/cache.php | 3 + resources/graph-uml/callback/closure.php | 26 ++++++ resources/graph-uml/callback/config.php | 3 + resources/graph-uml/callback/console.php | 3 + resources/graph-uml/callback/event.php | 3 + resources/graph-uml/callback/extension.php | 3 + resources/graph-uml/callback/finder.php | 3 + resources/graph-uml/callback/helper.php | 3 + resources/graph-uml/callback/linter.php | 3 + resources/graph-uml/callback/output.php | 3 + resources/graph-uml/callback/process.php | 3 + resources/graph-uml/datasource/cache.php | 14 +++ resources/graph-uml/datasource/config.php | 26 ++++++ resources/graph-uml/datasource/console.php | 18 ++++ resources/graph-uml/datasource/event.php | 29 ++++++ resources/graph-uml/datasource/extension.php | 19 ++++ resources/graph-uml/datasource/finder.php | 13 +++ resources/graph-uml/datasource/helper.php | 15 ++++ resources/graph-uml/datasource/linter.php | 13 +++ resources/graph-uml/datasource/output.php | 25 ++++++ resources/graph-uml/datasource/process.php | 15 ++++ resources/graph-uml/filter/cache.php | 28 ++++++ resources/graph-uml/filter/config.php | 29 ++++++ resources/graph-uml/filter/console.php | 29 ++++++ resources/graph-uml/filter/event.php | 29 ++++++ resources/graph-uml/filter/extension.php | 29 ++++++ resources/graph-uml/filter/finder.php | 29 ++++++ resources/graph-uml/filter/helper.php | 29 ++++++ resources/graph-uml/filter/linter.php | 28 ++++++ resources/graph-uml/filter/output.php | 29 ++++++ resources/graph-uml/filter/process.php | 29 ++++++ resources/graph-uml/options/cache.php | 3 + resources/graph-uml/options/common.php | 8 ++ resources/graph-uml/options/config.php | 5 ++ resources/graph-uml/options/console.php | 5 ++ resources/graph-uml/options/event.php | 5 ++ resources/graph-uml/options/extension.php | 3 + resources/graph-uml/options/finder.php | 5 ++ resources/graph-uml/options/helper.php | 5 ++ resources/graph-uml/options/linter.php | 3 + resources/graph-uml/options/output.php | 5 ++ resources/graph-uml/options/process.php | 5 ++ 45 files changed, 687 insertions(+) create mode 100755 resources/gh-pages-hook.sh create mode 100755 resources/graph-uml.phar create mode 100644 resources/graph-uml/build.php create mode 100644 resources/graph-uml/callback/cache.php create mode 100644 resources/graph-uml/callback/closure.php create mode 100644 resources/graph-uml/callback/config.php create mode 100644 resources/graph-uml/callback/console.php create mode 100644 resources/graph-uml/callback/event.php create mode 100644 resources/graph-uml/callback/extension.php create mode 100644 resources/graph-uml/callback/finder.php create mode 100644 resources/graph-uml/callback/helper.php create mode 100644 resources/graph-uml/callback/linter.php create mode 100644 resources/graph-uml/callback/output.php create mode 100644 resources/graph-uml/callback/process.php create mode 100644 resources/graph-uml/datasource/cache.php create mode 100644 resources/graph-uml/datasource/config.php create mode 100644 resources/graph-uml/datasource/console.php create mode 100644 resources/graph-uml/datasource/event.php create mode 100644 resources/graph-uml/datasource/extension.php create mode 100644 resources/graph-uml/datasource/finder.php create mode 100644 resources/graph-uml/datasource/helper.php create mode 100644 resources/graph-uml/datasource/linter.php create mode 100644 resources/graph-uml/datasource/output.php create mode 100644 resources/graph-uml/datasource/process.php create mode 100644 resources/graph-uml/filter/cache.php create mode 100644 resources/graph-uml/filter/config.php create mode 100644 resources/graph-uml/filter/console.php create mode 100644 resources/graph-uml/filter/event.php create mode 100644 resources/graph-uml/filter/extension.php create mode 100644 resources/graph-uml/filter/finder.php create mode 100644 resources/graph-uml/filter/helper.php create mode 100644 resources/graph-uml/filter/linter.php create mode 100644 resources/graph-uml/filter/output.php create mode 100644 resources/graph-uml/filter/process.php create mode 100644 resources/graph-uml/options/cache.php create mode 100644 resources/graph-uml/options/common.php create mode 100644 resources/graph-uml/options/config.php create mode 100644 resources/graph-uml/options/console.php create mode 100644 resources/graph-uml/options/event.php create mode 100644 resources/graph-uml/options/extension.php create mode 100644 resources/graph-uml/options/finder.php create mode 100644 resources/graph-uml/options/helper.php create mode 100644 resources/graph-uml/options/linter.php create mode 100644 resources/graph-uml/options/output.php create mode 100644 resources/graph-uml/options/process.php diff --git a/resources/gh-pages-hook.sh b/resources/gh-pages-hook.sh new file mode 100755 index 00000000..17560839 --- /dev/null +++ b/resources/gh-pages-hook.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +ASSETS_IMAGE_DIR="docs/assets" + +php $SCRIPT_DIR/graph-uml/build.php cache $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php config $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php console $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php event $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php extension $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php finder $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php helper $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php linter $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php output $ASSETS_IMAGE_DIR +php $SCRIPT_DIR/graph-uml/build.php process $ASSETS_IMAGE_DIR diff --git a/resources/graph-uml.phar b/resources/graph-uml.phar new file mode 100755 index 0000000000000000000000000000000000000000..fe55587c3e5f61c5d6289bb6f5417548d9b10e52 GIT binary patch literal 106566 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, dataSource()); +} catch (Exception $e) { + echo 'Unable to build graph UML : ' . $e->getMessage() . PHP_EOL; + die(); +} + +// For large graph, orientation is recommended +// https://graphviz.gitlab.io/docs/attrs/rankdir/ +$graph->setAttribute($generator->getPrefix() . 'graph.rankdir', $options['graph.rankdir'] ?? 'LR'); +// https://graphviz.gitlab.io/docs/attrs/bgcolor/ +$graph->setAttribute($generator->getPrefix() . 'graph.bgcolor', $options['graph.bgcolor'] ?? 'transparent'); +// https://graphviz.gitlab.io/docs/attrs/fillcolor/ +$graph->setAttribute($generator->getPrefix() . 'node.fillcolor', $options['node.fillcolor'] ?? '#FEFECE'); +// https://graphviz.gitlab.io/docs/attrs/style/ +$graph->setAttribute($generator->getPrefix() . 'node.style', $options['node.style'] ?? 'filled'); + +// writes graphviz statements to file +$folder = $_SERVER['argv'][2] ?? null; +$format = sprintf('.%s.gv', $options['label_format']); +$output = rtrim($folder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $script . $format; +//file_put_contents($output, $generator->createScript($graph)); + +// default format is PNG, change it to SVG +$generator->setFormat($format = 'svg'); + +if (isset($folder)) { + $output = rtrim($folder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $script . '-uml-diagram.' . $format; + $cmdFormat = '%E -T%F %t -o ' . $output; +} else { + $cmdFormat = ''; +} +$target = $generator->createImageFile($graph, $cmdFormat); +echo (empty($target) ? 'no' : $target) . ' file generated' . PHP_EOL; diff --git a/resources/graph-uml/callback/cache.php b/resources/graph-uml/callback/cache.php new file mode 100644 index 00000000..028dc9b7 --- /dev/null +++ b/resources/graph-uml/callback/cache.php @@ -0,0 +1,3 @@ +createVertexClass($fqcn); + + $namespaceFilter = $options['namespace_filter']; + if ($namespaceFilter instanceof NamespaceFilterInterface) { + $cluster = $namespaceFilter->filter($fqcn); + if (null !== $cluster) { + // highlight this specific element + $color = 'burlywood3'; + } else { + $color = 'white'; + } + $graph->setAttribute( + $generator->getPrefix() . sprintf('cluster.%s.graph.bgcolor', $cluster), + $color + ); + } + } +}; diff --git a/resources/graph-uml/callback/config.php b/resources/graph-uml/callback/config.php new file mode 100644 index 00000000..028dc9b7 --- /dev/null +++ b/resources/graph-uml/callback/config.php @@ -0,0 +1,3 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 2 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/config.php b/resources/graph-uml/filter/config.php new file mode 100644 index 00000000..1daa15c8 --- /dev/null +++ b/resources/graph-uml/filter/config.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Configuration' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/console.php b/resources/graph-uml/filter/console.php new file mode 100644 index 00000000..314e2d19 --- /dev/null +++ b/resources/graph-uml/filter/console.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 2 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && in_array($nameParts[2], ['Command', 'Console']) + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/event.php b/resources/graph-uml/filter/event.php new file mode 100644 index 00000000..184179d9 --- /dev/null +++ b/resources/graph-uml/filter/event.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Event' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/extension.php b/resources/graph-uml/filter/extension.php new file mode 100644 index 00000000..886abed2 --- /dev/null +++ b/resources/graph-uml/filter/extension.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Extension' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/finder.php b/resources/graph-uml/filter/finder.php new file mode 100644 index 00000000..c4ba3578 --- /dev/null +++ b/resources/graph-uml/filter/finder.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) > 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Finder' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/helper.php b/resources/graph-uml/filter/helper.php new file mode 100644 index 00000000..710305b8 --- /dev/null +++ b/resources/graph-uml/filter/helper.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Helper' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/linter.php b/resources/graph-uml/filter/linter.php new file mode 100644 index 00000000..60cde8f4 --- /dev/null +++ b/resources/graph-uml/filter/linter.php @@ -0,0 +1,28 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 2 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/output.php b/resources/graph-uml/filter/output.php new file mode 100644 index 00000000..d023888d --- /dev/null +++ b/resources/graph-uml/filter/output.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Output' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/filter/process.php b/resources/graph-uml/filter/process.php new file mode 100644 index 00000000..b76c2464 --- /dev/null +++ b/resources/graph-uml/filter/process.php @@ -0,0 +1,29 @@ +shortClass = array_pop($nameParts); // removes short name part of Fully Qualified Class Name + + if ( + count($nameParts) >= 3 + && $nameParts[0] == 'Overtrue' + && $nameParts[1] == 'PHPLint' + && $nameParts[2] == 'Process' + ) { + return implode('\\', $nameParts); + } + return null; + } + + public function getShortClass(): ?string + { + return $this->shortClass; + } +}; diff --git a/resources/graph-uml/options/cache.php b/resources/graph-uml/options/cache.php new file mode 100644 index 00000000..8d7a5dfe --- /dev/null +++ b/resources/graph-uml/options/cache.php @@ -0,0 +1,3 @@ + 'html', + 'show_private' => false, + 'show_protected' => false, + 'namespace_filter' => $namespaceFilter, +]; diff --git a/resources/graph-uml/options/config.php b/resources/graph-uml/options/config.php new file mode 100644 index 00000000..d8980b29 --- /dev/null +++ b/resources/graph-uml/options/config.php @@ -0,0 +1,5 @@ + Date: Sun, 17 Dec 2023 15:47:36 +0000 Subject: [PATCH 49/53] update Architecture documentation related to version 9.1 --- docs/architecture/README.md | 8 + docs/architecture/cache.md | 4 +- docs/architecture/configuration.md | 4 +- docs/architecture/console.md | 12 + docs/architecture/event.md | 4 +- docs/architecture/extension.md | 4 +- docs/architecture/finder.md | 4 +- docs/architecture/helper.md | 17 + docs/architecture/output.md | 6 +- docs/architecture/process.md | 6 +- docs/assets/cache-uml-diagram.svg | 42 +- docs/assets/config-uml-diagram.svg | 230 +++++------ docs/assets/console-uml-diagram.svg | 200 ++++++++++ docs/assets/event-uml-diagram.svg | 538 +++++++++++++------------- docs/assets/extension-uml-diagram.svg | 311 ++++++++------- docs/assets/finder-uml-diagram.svg | 52 ++- docs/assets/helper-uml-diagram.svg | 102 +++++ docs/assets/linter-uml-diagram.svg | 29 ++ docs/assets/output-uml-diagram.svg | 499 +++++++++++++----------- docs/assets/process-uml-diagram.svg | 275 ++++++------- 20 files changed, 1421 insertions(+), 926 deletions(-) create mode 100644 docs/architecture/console.md create mode 100644 docs/architecture/helper.md create mode 100644 docs/assets/console-uml-diagram.svg create mode 100644 docs/assets/helper-uml-diagram.svg create mode 100644 docs/assets/linter-uml-diagram.svg diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 6e22937a..c6224be8 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -10,6 +10,10 @@ To learn how PHPLint is able to improve speed analysis on multiple runs. To learn how PHPLint options can customize your checks. +## [Console](console.md#console) + +To learn more about PHPLint CLI application. + ## [Event-Driven](event.md#event-driven-architecture-on-wikipediaeda) To learn more about how to extend PHPLint features. @@ -22,6 +26,10 @@ To learn more about how to extend PHPLint features. To learn how PHPLint find files to check. +## [Helper](helper.md#helper) + +To learn how PHPLint is able to debug asynchronous processes run in background. + ## [Output](output.md#output-formats) To learn how PHPLint can customize results output. diff --git a/docs/architecture/cache.md b/docs/architecture/cache.md index ac93c20d..40a158d1 100644 --- a/docs/architecture/cache.md +++ b/docs/architecture/cache.md @@ -17,8 +17,8 @@ You can change this directory with the `cache` option. See [Configuration](../co ![UML Diagram](../assets/cache-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/cache]: https://github.com/symfony/cache [filesystem-adapter]: https://symfony.com/doc/current/components/cache/adapters/filesystem_adapter.html diff --git a/docs/architecture/configuration.md b/docs/architecture/configuration.md index 985b8f27..487fbb87 100644 --- a/docs/architecture/configuration.md +++ b/docs/architecture/configuration.md @@ -20,8 +20,8 @@ When YAML configuration file exists and is loadable, it will be automatically us ![UML Diagram](../assets/config-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/options-resolver]: https://github.com/symfony/options-resolver [factory-method-pattern]: https://en.wikipedia.org/wiki/Factory_method_pattern diff --git a/docs/architecture/console.md b/docs/architecture/console.md new file mode 100644 index 00000000..6cb7859d --- /dev/null +++ b/docs/architecture/console.md @@ -0,0 +1,12 @@ +# Console + +PHPLint is a CLI Application with a single command built over the [Symfony Console Component][symfony/console]. + +## UML Diagram + +![UML Diagram](../assets/console-uml-diagram.svg) + +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. + +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml +[symfony/console]: https://symfony.com/doc/current/components/console.html diff --git a/docs/architecture/event.md b/docs/architecture/event.md index 4f1f96b1..348e58b3 100644 --- a/docs/architecture/event.md +++ b/docs/architecture/event.md @@ -9,7 +9,7 @@ That's allow to easily add new `Extension` (like progress `bar` and `meter` widg ![UML Diagram](../assets/event-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. ## The Dispatcher @@ -40,6 +40,6 @@ $dispatcher = new EventDispatcher($extensions); ``` [eda]: https://en.wikipedia.org/wiki/Event-driven_architecture -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/event-dispatcher]: https://github.com/symfony/event-dispatcher [EventSubscriberInterface]: https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php diff --git a/docs/architecture/extension.md b/docs/architecture/extension.md index 123ede03..7d62ccfa 100644 --- a/docs/architecture/extension.md +++ b/docs/architecture/extension.md @@ -7,7 +7,7 @@ Each extension is based on [Event Driven Architecture](./event.md) ![UML Diagram](../assets/extension-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. ## OutputFormat @@ -97,6 +97,6 @@ $extensions = [ ``` -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony-progressbar]: https://symfony.com/doc/current/components/console/helpers/progressbar.html [symfony-progressindicator]: https://symfony.com/doc/current/components/console/helpers/progressindicator.html \ No newline at end of file diff --git a/docs/architecture/finder.md b/docs/architecture/finder.md index 8b46f51b..225cc065 100644 --- a/docs/architecture/finder.md +++ b/docs/architecture/finder.md @@ -12,7 +12,7 @@ you can replace the `Overtrue\PHPLint\Finder` object by any Symfony Finder insta ![UML Diagram](../assets/finder-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/finder]: https://github.com/symfony/finder diff --git a/docs/architecture/helper.md b/docs/architecture/helper.md new file mode 100644 index 00000000..0eeeee08 --- /dev/null +++ b/docs/architecture/helper.md @@ -0,0 +1,17 @@ +# Helper + +PHPLint uses two adapted Symfony Console helpers +([ProcessHelper][symfony/process-helper] and [DebugFormatterHelper][symfony/debug-formatter-helper]) +for debugging asynchronous processes. + +You've just to use verbose level 2 or 3 to see live results. + +## UML Diagram + +![UML Diagram](../assets/helper-uml-diagram.svg) + +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. + +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml +[symfony/process-helper]: https://symfony.com/doc/current/components/console/helpers/processhelper.html +[symfony/debug-formatter-helper]: https://symfony.com/doc/current/components/console/helpers/debug_formatter.html diff --git a/docs/architecture/output.md b/docs/architecture/output.md index 2ed34c80..0449013b 100644 --- a/docs/architecture/output.md +++ b/docs/architecture/output.md @@ -12,9 +12,9 @@ via the `Overtrue\PHPLint\Output\ChainOutput` object and its handlers (`Overtrue ## UML Diagram -![UML Diagram](../assets/extension-uml-diagram.svg) +![UML Diagram](../assets/output-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. ## `ConsoleOutput` handler @@ -88,7 +88,7 @@ This handler is responsible to print PHPLint results on Junit XML format. For ex ``` -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/console]: https://github.com/symfony/console [symfony-console-events]: https://symfony.com/doc/current/components/console/events.html [chain-of-responsibility-pattern]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern diff --git a/docs/architecture/process.md b/docs/architecture/process.md index f6e706c7..693116fe 100644 --- a/docs/architecture/process.md +++ b/docs/architecture/process.md @@ -7,9 +7,9 @@ and build one `Overtrue\PHPLint\Process\LintProcess` instance for each command/f ## UML Diagram -![UML Diagram](../assets/extension-uml-diagram.svg) +![UML Diagram](../assets/process-uml-diagram.svg) -Generated by [bartlett/umlwriter][bartlett/umlwriter] package. +Generated by [bartlett/graph-uml][bartlett/graph-uml] package via the `resources/graph-uml/build.php` script. ## Example(s) @@ -51,5 +51,5 @@ $results = $linter->lintFiles($finder->getFiles()); // - list of file that were analyzed (`getMisses()`) because contents changed since previous run ``` -[bartlett/umlwriter]: https://github.com/llaville/umlwriter +[bartlett/graph-uml]: https://packagist.org/packages/bartlett/graph-uml [symfony/process]: https://github.com/symfony/process diff --git a/docs/assets/cache-uml-diagram.svg b/docs/assets/cache-uml-diagram.svg index 15715eca..5943fc92 100644 --- a/docs/assets/cache-uml-diagram.svg +++ b/docs/assets/cache-uml-diagram.svg @@ -1,36 +1,36 @@ - - - + + %3 cluster_0 - -Overtrue\PHPLint + +Overtrue\PHPLint Overtrue\\PHPLint\\Cache - - - -Cache - - -+ __construct(cachePoolAdapter = «unknown») -+ createCachePool(adapter : Symfony\Component\Cache\Adapter\AdapterInterface) : Psr\Cache\CacheItemPoolInterface -+ hasItem(filename : string) : bool -+ getItem(filename : string) : Psr\Cache\CacheItemInterface -+ saveItem(item : Psr\Cache\CacheItemInterface) : bool -+ clear(prefix : string = '') : bool -+ isHit(filename : string) : bool -+ getCalls() : array -+ __debugInfo() : ?array + + + +Cache + + ++ __construct(cachePoolAdapter = «unknown») ++ createCachePool(adapter : Symfony\Component\Cache\Adapter\AdapterInterface) : Psr\Cache\CacheItemPoolInterface ++ hasItem(filename : string) : bool ++ getItem(filename : string) : Psr\Cache\CacheItemInterface ++ saveItem(item : Psr\Cache\CacheItemInterface) : bool ++ clear(prefix : string = '') : bool ++ isHit(filename : string) : bool ++ getCalls() : array ++ __debugInfo() : ?array diff --git a/docs/assets/config-uml-diagram.svg b/docs/assets/config-uml-diagram.svg index f14491e0..26c54736 100644 --- a/docs/assets/config-uml-diagram.svg +++ b/docs/assets/config-uml-diagram.svg @@ -1,153 +1,155 @@ - - + %3 cluster_0 - -Overtrue\PHPLint\Configuration + +Overtrue\PHPLint\Configuration - + -Overtrue\\PHPLint\\Configuration\\Options - - - -«interface» -Options - - -+ «abstract» resolve() : array +Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver + + + +«abstract» +AbstractOptionsResolver + + ++ __construct(input : Symfony\Component\Console\Input\InputInterface, configuration : array = []) ++ «abstract» factory() : Overtrue\PHPLint\Configuration\Options ++ getOptions() : array ++ getOption(name : string) : ?mixed Overtrue\\PHPLint\\Configuration\\Resolver - - - -«interface» -Resolver - - -+ «abstract» factory() : Overtrue\PHPLint\Configuration\Options -+ «abstract» getOptions() : array -+ «abstract» getOption(name : string) : ?mixed + + + +«interface» +Resolver + + ++ «abstract» factory() : Overtrue\PHPLint\Configuration\Options ++ «abstract» getOptions() : array ++ «abstract» getOption(name : string) : ?mixed - + + +Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver->Overtrue\\PHPLint\\Configuration\\Resolver + + + + -Overtrue\\PHPLint\\Configuration\\FileOptionsResolver - - - -FileOptionsResolver - - -+ __construct(input : Symfony\Component\Console\Input\InputInterface) -+ factory() : Overtrue\PHPLint\Configuration\Options +Overtrue\\PHPLint\\Configuration\\ConsoleOptionsResolver + + + +ConsoleOptionsResolver + + ++ factory() : Overtrue\PHPLint\Configuration\Options - + + +Overtrue\\PHPLint\\Configuration\\ConsoleOptionsResolver->Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver + + + + -Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver - - - -«abstract» -AbstractOptionsResolver - - -+ __construct(input : Symfony\Component\Console\Input\InputInterface, configuration : array = []) -+ «abstract» factory() : Overtrue\PHPLint\Configuration\Options -+ getOptions() : array -+ getOption(name : string) : ?mixed +Overtrue\\PHPLint\\Configuration\\FileOptionsResolver + + + +FileOptionsResolver + + ++ __construct(input : Symfony\Component\Console\Input\InputInterface) ++ factory() : Overtrue\PHPLint\Configuration\Options - + Overtrue\\PHPLint\\Configuration\\FileOptionsResolver->Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver - - - - - -Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver->Overtrue\\PHPLint\\Configuration\\Resolver - - + + Overtrue\\PHPLint\\Configuration\\OptionDefinition - - - -«interface» -OptionDefinition - -+ «static» OPTION_JOBS : string = "jobs" {readOnly} -+ «static» OPTION_PATH : string = "path" {readOnly} -+ «static» OPTION_EXCLUDE : string = "exclude" {readOnly} -+ «static» OPTION_EXTENSIONS : string = "extensions" {readOnly} -+ «static» OPTION_WARNING : string = "warning" {readOnly} -+ «static» OPTION_CACHE : string = "cache" {readOnly} -+ «static» OPTION_NO_CACHE : string = "no\-cache" {readOnly} -+ «static» OPTION_CONFIG_FILE : string = "configuration" {readOnly} -+ «static» OPTION_NO_CONFIG_FILE : string = "no\-configuration" {readOnly} -+ «static» OPTION_MEMORY_LIMIT : string = "memory\-limit" {readOnly} -+ «static» OPTION_PROGRESS : string = "progress" {readOnly} -+ «static» OPTION_NO_PROGRESS : string = "no\-progress" {readOnly} -+ «static» OPTION_JSON_FILE : string = "log\-json" {readOnly} -+ «static» OPTION_JUNIT_FILE : string = "log\-junit" {readOnly} -+ «static» OPTION_IGNORE_EXIT_CODE : string = "ignore\-exit\-code" {readOnly} -+ «static» DEFAULT_JOBS : int = 5 {readOnly} -+ «static» DEFAULT_PATH : string = "\." {readOnly} -+ «static» DEFAULT_EXCLUDES : array = [] {readOnly} -+ «static» DEFAULT_EXTENSIONS : array = […] {readOnly} -+ «static» DEFAULT_CACHE_DIR : string = "\.phplint\.cache" {readOnly} -+ «static» DEFAULT_CONFIG_FILE : string = "\.phplint\.yml" {readOnly} -+ «static» DEFAULT_PROGRESS_WIDGET : string = "printer" {readOnly} -+ «static» DEFAULT_STANDARD_OUTPUT_LABEL : string = "standard\ output" {readOnly} -+ «static» DEFAULT_STANDARD_OUTPUT : string = "php\:\/\/stdout" {readOnly} - - + + + +«interface» +OptionDefinition + ++ «static» JOBS : string = "jobs" {readOnly} ++ «static» PATH : string = "path" {readOnly} ++ «static» EXCLUDE : string = "exclude" {readOnly} ++ «static» EXTENSIONS : string = "extensions" {readOnly} ++ «static» WARNING : string = "warning" {readOnly} ++ «static» CACHE : string = "cache" {readOnly} ++ «static» NO_CACHE : string = "no\-cache" {readOnly} ++ «static» CONFIGURATION : string = "configuration" {readOnly} ++ «static» NO_CONFIGURATION : string = "no\-configuration" {readOnly} ++ «static» OPTION_MEMORY_LIMIT : string = "memory\-limit" {readOnly} ++ «static» PROGRESS : string = "progress" {readOnly} ++ «static» NO_PROGRESS : string = "no\-progress" {readOnly} ++ «static» LOG_JSON : string = "log\-json" {readOnly} ++ «static» LOG_JUNIT : string = "log\-junit" {readOnly} ++ «static» IGNORE_EXIT_CODE : string = "ignore\-exit\-code" {readOnly} ++ «static» DEFAULT_JOBS : int = 5 {readOnly} ++ «static» DEFAULT_PATH : string = "\." {readOnly} ++ «static» DEFAULT_EXCLUDES : array = [] {readOnly} ++ «static» DEFAULT_EXTENSIONS : array = […] {readOnly} ++ «static» DEFAULT_CACHE_DIR : string = "\.phplint\.cache" {readOnly} ++ «static» DEFAULT_CONFIG_FILE : string = "\.phplint\.yml" {readOnly} ++ «static» DEFAULT_PROGRESS_WIDGET : string = "printer" {readOnly} ++ «static» DEFAULT_STANDARD_OUTPUT_LABEL : string = "standard\ output" {readOnly} ++ «static» DEFAULT_STANDARD_OUTPUT : string = "php\:\/\/stdout" {readOnly} + + - + -Overtrue\\PHPLint\\Configuration\\ConsoleOptionsResolver - - - -ConsoleOptionsResolver - - -+ factory() : Overtrue\PHPLint\Configuration\Options - - - -Overtrue\\PHPLint\\Configuration\\ConsoleOptionsResolver->Overtrue\\PHPLint\\Configuration\\AbstractOptionsResolver - - +Overtrue\\PHPLint\\Configuration\\Options + + + +«interface» +Options + + ++ «abstract» resolve() : array Overtrue\\PHPLint\\Configuration\\OptionsFactory - - - -OptionsFactory - - -+ __construct(defaults : array) -+ resolve() : array + + + +OptionsFactory + + ++ __construct(defaults : array) ++ resolve() : array ++ «static» logNormalizer(options : Symfony\Component\OptionsResolver\Options, value) ++ «static» toBool(value : mixed) Overtrue\\PHPLint\\Configuration\\OptionsFactory->Overtrue\\PHPLint\\Configuration\\Options - - + + diff --git a/docs/assets/console-uml-diagram.svg b/docs/assets/console-uml-diagram.svg new file mode 100644 index 00000000..adfb8f5d --- /dev/null +++ b/docs/assets/console-uml-diagram.svg @@ -0,0 +1,200 @@ + + + + + + +%3 + +cluster_0 + +Overtrue\PHPLint\Command + + +cluster_1 + +Symfony\Component\Console\Command + + +cluster_2 + +Overtrue\PHPLint\Console + + +cluster_3 + +Symfony\Component\Console + + +cluster_4 + +Symfony\Contracts\Service + + + +Overtrue\\PHPLint\\Command\\ConfigureCommandTrait + + + +ConfigureCommandTrait + + + + + +Overtrue\\PHPLint\\Command\\LintCommand + + + +LintCommand + + ++ __construct(dispatcher : Symfony\Component\EventDispatcher\EventDispatcherInterface, name : string = 'lint') ++ getResults() : Overtrue\PHPLint\Output\LinterOutput + + + +Symfony\\Component\\Console\\Command\\Command + + + +Command + ++ «static» SUCCESS : int = 0 {readOnly} ++ «static» FAILURE : int = 1 {readOnly} ++ «static» INVALID : int = 2 {readOnly} + + ++ «static» getDefaultName() : ?string ++ «static» getDefaultDescription() : ?string ++ __construct(name : string = «unknown») ++ ignoreValidationErrors() ++ setApplication(application : Symfony\Component\Console\Application = «unknown») ++ setHelperSet(helperSet : Symfony\Component\Console\Helper\HelperSet) ++ getHelperSet() : ?Symfony\Component\Console\Helper\HelperSet ++ getApplication() : ?Symfony\Component\Console\Application ++ isEnabled() ++ run(input : Symfony\Component\Console\Input\InputInterface, output : Symfony\Component\Console\Output\OutputInterface) : int ++ complete(input : Symfony\Component\Console\Completion\CompletionInput, suggestions : Symfony\Component\Console\Completion\CompletionSuggestions) : void ++ setCode(code : callable) : static ++ mergeApplicationDefinition(mergeArgs : bool = true) : void ++ setDefinition(definition) : static ++ getDefinition() : Symfony\Component\Console\Input\InputDefinition ++ getNativeDefinition() : Symfony\Component\Console\Input\InputDefinition ++ addArgument(name : string, mode : int = «unknown», description : string = '', default : mixed = «unknown») : static ++ addOption(name : string, shortcut = «unknown», mode : int = «unknown», description : string = '', default : mixed = «unknown») : static ++ setName(name : string) : static ++ setProcessTitle(title : string) : static ++ getName() : ?string ++ setHidden(hidden : bool = true) : static ++ isHidden() : bool ++ setDescription(description : string) : static ++ getDescription() : string ++ setHelp(help : string) : static ++ getHelp() : string ++ getProcessedHelp() : string ++ setAliases(aliases : iterable) : static ++ getAliases() : array ++ getSynopsis(short : bool = false) : string ++ addUsage(usage : string) : static ++ getUsages() : array ++ getHelper(name : string) : ?mixed + + + +Overtrue\\PHPLint\\Command\\LintCommand->Symfony\\Component\\Console\\Command\\Command + + + + + +Overtrue\\PHPLint\\Console\\Application + + + +Application + ++ «static» NAME : string = "phplint" {readOnly} ++ «static» VERSION : string = "9\.1\.0" {readOnly} + + ++ __construct() ++ run(input : Symfony\Component\Console\Input\InputInterface = «unknown», output : Symfony\Component\Console\Output\OutputInterface = «unknown») : int + + + +Symfony\\Component\\Console\\Application + + + +Application + + ++ __construct(name : string = 'UNKNOWN', version : string = 'UNKNOWN') ++ setDispatcher(dispatcher : Symfony\Contracts\EventDispatcher\EventDispatcherInterface) : void ++ setCommandLoader(commandLoader : Symfony\Component\Console\CommandLoader\CommandLoaderInterface) ++ getSignalRegistry() : Symfony\Component\Console\SignalRegistry\SignalRegistry ++ setSignalsToDispatchEvent(signalsToDispatchEvent : int = «unknown») ++ run(input : Symfony\Component\Console\Input\InputInterface = «unknown», output : Symfony\Component\Console\Output\OutputInterface = «unknown») : int ++ doRun(input : Symfony\Component\Console\Input\InputInterface, output : Symfony\Component\Console\Output\OutputInterface) ++ reset() ++ setHelperSet(helperSet : Symfony\Component\Console\Helper\HelperSet) ++ getHelperSet() : Symfony\Component\Console\Helper\HelperSet ++ setDefinition(definition : Symfony\Component\Console\Input\InputDefinition) ++ getDefinition() : Symfony\Component\Console\Input\InputDefinition ++ complete(input : Symfony\Component\Console\Completion\CompletionInput, suggestions : Symfony\Component\Console\Completion\CompletionSuggestions) : void ++ getHelp() : string ++ areExceptionsCaught() : bool ++ setCatchExceptions(boolean : bool) ++ setCatchErrors(catchErrors : bool = true) : void ++ isAutoExitEnabled() : bool ++ setAutoExit(boolean : bool) ++ getName() : string ++ setName(name : string) ++ getVersion() : string ++ setVersion(version : string) ++ getLongVersion() ++ register(name : string) : Symfony\Component\Console\Command\Command ++ addCommands(commands : array) ++ add(command : Symfony\Component\Console\Command\Command) ++ get(name : string) ++ has(name : string) : bool ++ getNamespaces() : array ++ findNamespace(namespace : string) : string ++ find(name : string) ++ all(namespace : string = «unknown») ++ «static» getAbbreviations(names : array) : array ++ renderThrowable(e : Throwable, output : Symfony\Component\Console\Output\OutputInterface) : void ++ extractNamespace(name : string, limit : int = «unknown») : string ++ setDefaultCommand(commandName : string, isSingleCommand : bool = false) : static ++ isSingleCommand() : bool + + + +Overtrue\\PHPLint\\Console\\Application->Symfony\\Component\\Console\\Application + + + + + +Symfony\\Contracts\\Service\\ResetInterface + + + +«interface» +ResetInterface + + ++ «abstract» reset() + + + +Symfony\\Component\\Console\\Application->Symfony\\Contracts\\Service\\ResetInterface + + + + + diff --git a/docs/assets/event-uml-diagram.svg b/docs/assets/event-uml-diagram.svg index be34c8f4..7484b70e 100644 --- a/docs/assets/event-uml-diagram.svg +++ b/docs/assets/event-uml-diagram.svg @@ -1,360 +1,360 @@ - - + %3 cluster_0 - -Overtrue\PHPLint\Event + +Overtrue\PHPLint\Event cluster_1 - -Symfony\Component\EventDispatcher + +Symfony\Component\EventDispatcher cluster_2 - -Symfony\Contracts\EventDispatcher + +Symfony\Contracts\EventDispatcher cluster_3 - -Psr\EventDispatcher + +Psr\EventDispatcher cluster_4 - -0 - - - -Overtrue\\PHPLint\\Event\\BeforeLintFileInterface - - - -«interface» -BeforeLintFileInterface - - -+ «abstract» beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void - - - -Overtrue\\PHPLint\\Event\\BeforeCheckingInterface - - - -«interface» -BeforeCheckingInterface - - -+ «abstract» beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void - - - -Overtrue\\PHPLint\\Event\\AfterLintFileEvent - - - -AfterLintFileEvent - - - - - -Symfony\\Component\\EventDispatcher\\GenericEvent - - - -GenericEvent - - -+ __construct(subject : mixed = «unknown», arguments : array = []) -+ getSubject() : ?mixed -+ getArgument(key : string) : ?mixed -+ setArgument(key : string, value : mixed) : static -+ getArguments() : array -+ setArguments(args : array = []) : static -+ hasArgument(key : string) : bool -+ offsetGet(key : mixed) : ?mixed -+ offsetSet(key : mixed, value : mixed) : void -+ offsetUnset(key : mixed) : void -+ offsetExists(key : mixed) : bool -+ getIterator() : ArrayIterator - - - -Overtrue\\PHPLint\\Event\\AfterLintFileEvent->Symfony\\Component\\EventDispatcher\\GenericEvent - - + +0 - + Overtrue\\PHPLint\\Event\\EventDispatcher - - - -EventDispatcher - - -+ __construct(extensions : iterable) -+ dispatch(event : object, eventName : string = «unknown») : object + + + +EventDispatcher + + ++ __construct(extensions : iterable) ++ dispatch(event : object, eventName : string = «unknown») : object - + Symfony\\Component\\EventDispatcher\\EventDispatcher - - - -EventDispatcher - - -+ __construct() -+ dispatch(event : object, eventName : string = «unknown») : object -+ getListeners(eventName : string = «unknown») : array -+ getListenerPriority(eventName : string, listener) : ?int -+ hasListeners(eventName : string = «unknown») : bool -+ addListener(eventName : string, listener, priority : int = 0) -+ removeListener(eventName : string, listener) -+ addSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) -+ removeSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) + + + +EventDispatcher + + ++ __construct() ++ dispatch(event : object, eventName : string = «unknown») : object ++ getListeners(eventName : string = «unknown») : array ++ getListenerPriority(eventName : string, listener) : ?int ++ hasListeners(eventName : string = «unknown») : bool ++ addListener(eventName : string, listener, priority : int = 0) ++ removeListener(eventName : string, listener) ++ addSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) ++ removeSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) - + Overtrue\\PHPLint\\Event\\EventDispatcher->Symfony\\Component\\EventDispatcher\\EventDispatcher - - + + - + Overtrue\\PHPLint\\Event\\AfterCheckingEvent - - - -AfterCheckingEvent - - + + + +AfterCheckingEvent + + + + + +Symfony\\Component\\EventDispatcher\\GenericEvent + + + +GenericEvent + + ++ __construct(subject : mixed = «unknown», arguments : array = []) ++ getSubject() : ?mixed ++ getArgument(key : string) : ?mixed ++ setArgument(key : string, value : mixed) : static ++ getArguments() : array ++ setArguments(args : array = []) : static ++ hasArgument(key : string) : bool ++ offsetGet(key : mixed) : ?mixed ++ offsetSet(key : mixed, value : mixed) : void ++ offsetUnset(key : mixed) : void ++ offsetExists(key : mixed) : bool ++ getIterator() : ArrayIterator - + Overtrue\\PHPLint\\Event\\AfterCheckingEvent->Symfony\\Component\\EventDispatcher\\GenericEvent - - + + + + + +Overtrue\\PHPLint\\Event\\AfterCheckingInterface + + + +«interface» +AfterCheckingInterface + + ++ «abstract» afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void + + + +Overtrue\\PHPLint\\Event\\AfterLintFileEvent + + + +AfterLintFileEvent + + + + + +Overtrue\\PHPLint\\Event\\AfterLintFileEvent->Symfony\\Component\\EventDispatcher\\GenericEvent + + + + + +Overtrue\\PHPLint\\Event\\AfterLintFileInterface + + + +«interface» +AfterLintFileInterface + + ++ «abstract» afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void Overtrue\\PHPLint\\Event\\BeforeCheckingEvent - - - -BeforeCheckingEvent - - + + + +BeforeCheckingEvent + + Overtrue\\PHPLint\\Event\\BeforeCheckingEvent->Symfony\\Component\\EventDispatcher\\GenericEvent - - + + - + +Overtrue\\PHPLint\\Event\\BeforeCheckingInterface + + + +«interface» +BeforeCheckingInterface + + ++ «abstract» beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void + + + Overtrue\\PHPLint\\Event\\BeforeLintFileEvent - - - -BeforeLintFileEvent - - + + + +BeforeLintFileEvent + + Overtrue\\PHPLint\\Event\\BeforeLintFileEvent->Symfony\\Component\\EventDispatcher\\GenericEvent - - + + - - -Overtrue\\PHPLint\\Event\\AfterLintFileInterface - - - -«interface» -AfterLintFileInterface - - -+ «abstract» afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void - - + -Overtrue\\PHPLint\\Event\\AfterCheckingInterface - - - -«interface» -AfterCheckingInterface - - -+ «abstract» afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void +Overtrue\\PHPLint\\Event\\BeforeLintFileInterface + + + +«interface» +BeforeLintFileInterface + + ++ «abstract» beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void - + + +Symfony\\Component\\EventDispatcher\\EventDispatcherInterface + + + +«interface» +EventDispatcherInterface + + ++ «abstract» addListener(eventName : string, listener : callable, priority : int = 0) ++ «abstract» addSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) ++ «abstract» removeListener(eventName : string, listener : callable) ++ «abstract» removeSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) ++ «abstract» getListeners(eventName : string = «unknown») : array ++ «abstract» getListenerPriority(eventName : string, listener : callable) : ?int ++ «abstract» hasListeners(eventName : string = «unknown») : bool + + + +Symfony\\Component\\EventDispatcher\\EventDispatcher->Symfony\\Component\\EventDispatcher\\EventDispatcherInterface + + + + +Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface + + + +«interface» +EventDispatcherInterface + + ++ «abstract» dispatch(event : object, eventName : string = «unknown») : object + + + +Symfony\\Component\\EventDispatcher\\EventDispatcherInterface->Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface + + + + + Symfony\\Contracts\\EventDispatcher\\Event - - - -Event - - -+ isPropagationStopped() : bool -+ stopPropagation() : void + + + +Event + + ++ isPropagationStopped() : bool ++ stopPropagation() : void - + Symfony\\Component\\EventDispatcher\\GenericEvent->Symfony\\Contracts\\EventDispatcher\\Event - - + + ArrayAccess - - - -«interface» -ArrayAccess - - -+ «abstract» offsetExists(offset : mixed) -+ «abstract» offsetGet(offset : mixed) -+ «abstract» offsetSet(offset : mixed, value : mixed) -+ «abstract» offsetUnset(offset : mixed) + + + +«interface» +ArrayAccess + + ++ «abstract» offsetExists(offset : mixed) ++ «abstract» offsetGet(offset : mixed) ++ «abstract» offsetSet(offset : mixed, value : mixed) ++ «abstract» offsetUnset(offset : mixed) - + Symfony\\Component\\EventDispatcher\\GenericEvent->ArrayAccess - - + + IteratorAggregate - - - -«interface» -IteratorAggregate - - -+ «abstract» getIterator() + + + +«interface» +IteratorAggregate + + ++ «abstract» getIterator() - -Symfony\\Component\\EventDispatcher\\GenericEvent->IteratorAggregate - - - - - -Symfony\\Component\\EventDispatcher\\EventDispatcherInterface - - - -«interface» -EventDispatcherInterface - - -+ «abstract» addListener(eventName : string, listener : callable, priority : int = 0) -+ «abstract» addSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) -+ «abstract» removeListener(eventName : string, listener : callable) -+ «abstract» removeSubscriber(subscriber : Symfony\Component\EventDispatcher\EventSubscriberInterface) -+ «abstract» getListeners(eventName : string = «unknown») : array -+ «abstract» getListenerPriority(eventName : string, listener : callable) : ?int -+ «abstract» hasListeners(eventName : string = «unknown») : bool - - -Symfony\\Component\\EventDispatcher\\EventDispatcher->Symfony\\Component\\EventDispatcher\\EventDispatcherInterface - - +Symfony\\Component\\EventDispatcher\\GenericEvent->IteratorAggregate + + - - -Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface - - - -«interface» -EventDispatcherInterface - - -+ «abstract» dispatch(event : object, eventName : string = «unknown») : object + + +Psr\\EventDispatcher\\EventDispatcherInterface + + + +«interface» +EventDispatcherInterface + + ++ «abstract» dispatch(event : object) - - -Symfony\\Component\\EventDispatcher\\EventDispatcherInterface->Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface - - + + +Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface->Psr\\EventDispatcher\\EventDispatcherInterface + + - + Psr\\EventDispatcher\\StoppableEventInterface - - - -«interface» -StoppableEventInterface - - -+ «abstract» isPropagationStopped() : bool + + + +«interface» +StoppableEventInterface + + ++ «abstract» isPropagationStopped() : bool - + Symfony\\Contracts\\EventDispatcher\\Event->Psr\\EventDispatcher\\StoppableEventInterface - - - - - -Psr\\EventDispatcher\\EventDispatcherInterface - - - -«interface» -EventDispatcherInterface - - -+ «abstract» dispatch(event : object) - - - -Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface->Psr\\EventDispatcher\\EventDispatcherInterface - - + + Traversable - - - -«interface» -Traversable - - + + + +«interface» +Traversable + + - + IteratorAggregate->Traversable - - + + diff --git a/docs/assets/extension-uml-diagram.svg b/docs/assets/extension-uml-diagram.svg index e77a6def..a3cd6ab8 100644 --- a/docs/assets/extension-uml-diagram.svg +++ b/docs/assets/extension-uml-diagram.svg @@ -1,191 +1,230 @@ - - - + + %3 cluster_0 - -Overtrue\PHPLint\Extension + +Overtrue\PHPLint\Extension cluster_1 - -Symfony\Component\EventDispatcher + +Symfony\Component\EventDispatcher cluster_2 - -Overtrue\PHPLint\Event + +Overtrue\PHPLint\Event - + -Overtrue\\PHPLint\\Extension\\ProgressBar - - - -ProgressBar - - -+ «static» getSubscribedEvents() : array -+ initProgress(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void -+ beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void -+ afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void -+ beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void -+ afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void +Overtrue\\PHPLint\\Extension\\OutputFormat + + + +OutputFormat + + ++ __construct(outputOptions : array = []) ++ «static» getSubscribedEvents() : array ++ initFormat(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void ++ afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void - + Symfony\\Component\\EventDispatcher\\EventSubscriberInterface - - - -«interface» -EventSubscriberInterface - - -+ «abstract» «static» getSubscribedEvents() + + + +«interface» +EventSubscriberInterface + + ++ «abstract» «static» getSubscribedEvents() - + -Overtrue\\PHPLint\\Extension\\ProgressBar->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface - - - - - -Overtrue\\PHPLint\\Event\\BeforeCheckingInterface - - - -«interface» -BeforeCheckingInterface - - -+ «abstract» beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void - - - -Overtrue\\PHPLint\\Extension\\ProgressBar->Overtrue\\PHPLint\\Event\\BeforeCheckingInterface - - +Overtrue\\PHPLint\\Extension\\OutputFormat->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface + + Overtrue\\PHPLint\\Event\\AfterCheckingInterface - - - -«interface» -AfterCheckingInterface - - -+ «abstract» afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void + + + +«interface» +AfterCheckingInterface + + ++ «abstract» afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void - + + +Overtrue\\PHPLint\\Extension\\OutputFormat->Overtrue\\PHPLint\\Event\\AfterCheckingInterface + + + + + +Overtrue\\PHPLint\\Extension\\ProgressBar + + + +ProgressBar + + ++ «static» getSubscribedEvents() : array ++ initProgress(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void ++ beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void ++ afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void ++ beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void ++ afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void + + +Overtrue\\PHPLint\\Extension\\ProgressBar->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface + + + + + Overtrue\\PHPLint\\Extension\\ProgressBar->Overtrue\\PHPLint\\Event\\AfterCheckingInterface - - + + - + +Overtrue\\PHPLint\\Event\\BeforeCheckingInterface + + + +«interface» +BeforeCheckingInterface + + ++ «abstract» beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void + + + +Overtrue\\PHPLint\\Extension\\ProgressBar->Overtrue\\PHPLint\\Event\\BeforeCheckingInterface + + + + + Overtrue\\PHPLint\\Event\\BeforeLintFileInterface - - - -«interface» -BeforeLintFileInterface - - -+ «abstract» beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void + + + +«interface» +BeforeLintFileInterface + + ++ «abstract» beforeLintFile(event : Overtrue\PHPLint\Event\BeforeLintFileEvent) : void - + Overtrue\\PHPLint\\Extension\\ProgressBar->Overtrue\\PHPLint\\Event\\BeforeLintFileInterface - - + + - + Overtrue\\PHPLint\\Event\\AfterLintFileInterface - - - -«interface» -AfterLintFileInterface - - -+ «abstract» afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void + + + +«interface» +AfterLintFileInterface + + ++ «abstract» afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void - + Overtrue\\PHPLint\\Extension\\ProgressBar->Overtrue\\PHPLint\\Event\\AfterLintFileInterface - - + + + + + +Overtrue\\PHPLint\\Extension\\ProgressIndicator + + + +ProgressIndicator + + ++ «static» getSubscribedEvents() : array ++ initProgress(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void ++ beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void ++ afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void ++ afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void + + + +Overtrue\\PHPLint\\Extension\\ProgressIndicator->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface + + + + + +Overtrue\\PHPLint\\Extension\\ProgressIndicator->Overtrue\\PHPLint\\Event\\AfterCheckingInterface + + + + + +Overtrue\\PHPLint\\Extension\\ProgressIndicator->Overtrue\\PHPLint\\Event\\BeforeCheckingInterface + + + + + +Overtrue\\PHPLint\\Extension\\ProgressIndicator->Overtrue\\PHPLint\\Event\\AfterLintFileInterface + + - + Overtrue\\PHPLint\\Extension\\ProgressPrinter - - - -ProgressPrinter - - -+ «static» getSubscribedEvents() : array -+ initProgress(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void -+ beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void -+ afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void + + + +ProgressPrinter + + ++ «static» getSubscribedEvents() : array ++ initProgress(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void ++ beforeChecking(event : Overtrue\PHPLint\Event\BeforeCheckingEvent) : void ++ afterLintFile(event : Overtrue\PHPLint\Event\AfterLintFileEvent) : void - + Overtrue\\PHPLint\\Extension\\ProgressPrinter->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface - - + + - + Overtrue\\PHPLint\\Extension\\ProgressPrinter->Overtrue\\PHPLint\\Event\\BeforeCheckingInterface - - + + - + Overtrue\\PHPLint\\Extension\\ProgressPrinter->Overtrue\\PHPLint\\Event\\AfterLintFileInterface - - - - - -Overtrue\\PHPLint\\Extension\\OutputFormat - - - -OutputFormat - - -+ __construct(outputOptions : array = []) -+ «static» getSubscribedEvents() : array -+ initFormat(event : Symfony\Component\Console\Event\ConsoleCommandEvent) : void -+ afterChecking(event : Overtrue\PHPLint\Event\AfterCheckingEvent) : void - - - -Overtrue\\PHPLint\\Extension\\OutputFormat->Symfony\\Component\\EventDispatcher\\EventSubscriberInterface - - - - - -Overtrue\\PHPLint\\Extension\\OutputFormat->Overtrue\\PHPLint\\Event\\AfterCheckingInterface - - + + diff --git a/docs/assets/finder-uml-diagram.svg b/docs/assets/finder-uml-diagram.svg index fc5ca36d..5cdfd3dd 100644 --- a/docs/assets/finder-uml-diagram.svg +++ b/docs/assets/finder-uml-diagram.svg @@ -1,29 +1,53 @@ - - - + + %3 cluster_0 - -Overtrue\PHPLint + +Overtrue\PHPLint + + +cluster_1 + +0 Overtrue\\PHPLint\\Finder - - - -Finder - - -+ __construct(configResolver : Overtrue\PHPLint\Configuration\Resolver) -+ getFiles() : Symfony\Component\Finder\Finder + + + +Finder + + ++ __construct(configResolver : Overtrue\PHPLint\Configuration\Resolver) ++ jsonSerialize() : array ++ getFiles() : Symfony\Component\Finder\Finder + + + +JsonSerializable + + + +«interface» +JsonSerializable + + ++ «abstract» jsonSerialize() + + + +Overtrue\\PHPLint\\Finder->JsonSerializable + + diff --git a/docs/assets/helper-uml-diagram.svg b/docs/assets/helper-uml-diagram.svg new file mode 100644 index 00000000..b8f5850b --- /dev/null +++ b/docs/assets/helper-uml-diagram.svg @@ -0,0 +1,102 @@ + + + + + + +%3 + +cluster_0 + +Overtrue\PHPLint\Helper + + +cluster_1 + +Symfony\Component\Console\Helper + + + +Overtrue\\PHPLint\\Helper\\DebugFormatterHelper + + + +DebugFormatterHelper + ++ «static» COLORS : array = […] {readOnly} + + ++ getName() : string ++ start(id : string, message : string, prefix : string = 'RUN') : string ++ progress(id : string, buffer : string, error : bool = false, prefix : string = 'OUT', errorPrefix : string = 'ERR') : string ++ stop(id : string, message : string, successful : bool, prefix : string = 'RES') : string + + + +Symfony\\Component\\Console\\Helper\\Helper + + + +«abstract» +Helper + + ++ setHelperSet(helperSet : Symfony\Component\Console\Helper\HelperSet = «unknown») ++ getHelperSet() : ?Symfony\Component\Console\Helper\HelperSet ++ «static» width(string : string) : int ++ «static» length(string : string) : int ++ «static» substr(string : string, from : int, length : int = «unknown») : string ++ «static» formatTime(secs, precision : int = 1) ++ «static» formatMemory(memory : int) ++ «static» removeDecoration(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface, string : string) + + + +Overtrue\\PHPLint\\Helper\\DebugFormatterHelper->Symfony\\Component\\Console\\Helper\\Helper + + + + + +Overtrue\\PHPLint\\Helper\\ProcessHelper + + + +ProcessHelper + + ++ getName() : string ++ start(output : Symfony\Component\Console\Output\OutputInterface, process : Symfony\Component\Process\Process, callback : callable = «unknown», env : array = [], verbosity : int = Symfony\Component\Console\Output\OutputInterface\:\:VERBOSITY_VERY_VERBOSE) : Symfony\Component\Process\Process ++ isTerminated(output : Symfony\Component\Console\Output\OutputInterface, process : Symfony\Component\Process\Process, verbosity : int = Symfony\Component\Console\Output\OutputInterface\:\:VERBOSITY_VERY_VERBOSE) : bool + + + +Overtrue\\PHPLint\\Helper\\ProcessHelper->Symfony\\Component\\Console\\Helper\\Helper + + + + + +Symfony\\Component\\Console\\Helper\\HelperInterface + + + +«interface» +HelperInterface + + ++ «abstract» setHelperSet(helperSet : Symfony\Component\Console\Helper\HelperSet) ++ «abstract» getHelperSet() : ?Symfony\Component\Console\Helper\HelperSet ++ «abstract» getName() + + + +Symfony\\Component\\Console\\Helper\\Helper->Symfony\\Component\\Console\\Helper\\HelperInterface + + + + + diff --git a/docs/assets/linter-uml-diagram.svg b/docs/assets/linter-uml-diagram.svg new file mode 100644 index 00000000..c41e620c --- /dev/null +++ b/docs/assets/linter-uml-diagram.svg @@ -0,0 +1,29 @@ + + + + + + +%3 + +cluster_0 + +Overtrue\PHPLint + + + +Overtrue\\PHPLint\\Linter + + + +Linter + + ++ __construct(configResolver : Overtrue\PHPLint\Configuration\Resolver, dispatcher : Symfony\Component\EventDispatcher\EventDispatcherInterface, appVersion : string = '9\.1\.x\-dev', helperSet : Symfony\Component\Console\Helper\HelperSet = «unknown», output : Symfony\Component\Console\Output\OutputInterface = «unknown») ++ lintFiles(finder : Symfony\Component\Finder\Finder, startTime : float = «unknown») : Overtrue\PHPLint\Output\LinterOutput + + + diff --git a/docs/assets/output-uml-diagram.svg b/docs/assets/output-uml-diagram.svg index 8937c1a2..110fd739 100644 --- a/docs/assets/output-uml-diagram.svg +++ b/docs/assets/output-uml-diagram.svg @@ -1,307 +1,362 @@ - - - + + %3 cluster_0 - -Overtrue\PHPLint\Output + +Overtrue\PHPLint\Output cluster_1 - -Symfony\Component\Console\Output + +Symfony\Component\Console\Output - - -Overtrue\\PHPLint\\Output\\OutputInterface - - - -«interface» -OutputInterface - - -+ «abstract» format(results : Overtrue\PHPLint\Output\LinterOutput) : void - - - -Overtrue\\PHPLint\\Output\\LinterOutput - - - -LinterOutput - - -+ __construct(results : array, finder : Symfony\Component\Finder\Finder) -+ getContext() : array -+ setContext(configResolver : Overtrue\PHPLint\Configuration\Resolver, startTime : float) : void -+ hasFailures() : bool -+ getFailures() : array -+ hasErrors() : bool -+ getErrors() : array -+ hasWarnings() : bool -+ getWarnings() : array -+ getHits() : array -+ getMisses() : array + +cluster_2 + +0 - + Overtrue\\PHPLint\\Output\\ChainOutput - - - -ChainOutput - - -+ __construct(handlers : array) -+ format(results : Overtrue\PHPLint\Output\LinterOutput) : void + + + +ChainOutput + + ++ __construct(handlers : array) ++ format(results : Overtrue\PHPLint\Output\LinterOutput) : void + + + +Overtrue\\PHPLint\\Output\\OutputInterface + + + +«interface» +OutputInterface + + ++ «abstract» format(results : Overtrue\PHPLint\Output\LinterOutput) : void Overtrue\\PHPLint\\Output\\ChainOutput->Overtrue\\PHPLint\\Output\\OutputInterface - - + + - + Overtrue\\PHPLint\\Output\\ConsoleOutput - - - -ConsoleOutput - -+ «static» MAX_LINE_LENGTH : int = 120 {readOnly} - - -+ __construct(verbosity : int = parent\:\:VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») -+ setApplicationVersion(version : string) : void -+ setConfigResolver(resolver : Overtrue\PHPLint\Configuration\Resolver) : void -+ format(results : Overtrue\PHPLint\Output\LinterOutput) : void -+ createProgressBar(max = 0) : Symfony\Component\Console\Helper\ProgressBar -+ progressStart(max : int = 0) -+ progressAdvance(step : int = 1) -+ progressFinish() : void -+ progressMessage(message : string, name : string = 'message') -+ progressPrinterAdvance(maxSteps : int, status : string, fileInfo : Symfony\Component\Finder\SplFileInfo) : void -+ headerBlock(appVersion : string, configFile : string) : void -+ configBlock(options : array) : void -+ consumeBlock(timeUsage : string, memUsage : string, cacheUsage : string) : void -+ errorBlock(fileCount : int, errorCount : int) : void -+ successBlock(fileCount : int) : void -+ warningBlock(message : string = 'Could\ not\ find\ files\ to\ lint') : void -+ newLine(count : int = 1) + + + +ConsoleOutput + ++ «static» MAX_LINE_LENGTH : int = 120 {readOnly} ++ «static» NO_FILE_TO_LINT : string = "Could\ not\ find\ any\ files\ to\ lint" {readOnly} + + ++ __construct(verbosity : int = parent\:\:VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») ++ format(results : Overtrue\PHPLint\Output\LinterOutput) : void ++ createProgressBar(max = 0) : Symfony\Component\Console\Helper\ProgressBar ++ progressStart(max : int = 0) : void ++ progressAdvance(step : int = 1) : void ++ progressFinish() : void ++ progressMessage(message : string, name : string = 'message') : void ++ progressPrinterAdvance(maxSteps : int, status : string, fileInfo : Symfony\Component\Finder\SplFileInfo, step : int = 1) : void ++ headerBlock(appVersion : string, configFile : string) : void ++ configBlock(options : array) : void ++ consumeBlock(timeUsage : string, memUsage : string, cacheUsage : string, processCount : int) : void ++ errorBlock(fileCount : int, errorCount : int) : void ++ successBlock(fileCount : int) : void ++ warningBlock(message : string = NO_FILE_TO_LINT) : void ++ newLine(count : int = 1) : void - - -Overtrue\\PHPLint\\Output\\ConsoleOutput->Overtrue\\PHPLint\\Output\\OutputInterface - - + + +Overtrue\\PHPLint\\Output\\ConsoleOutputInterface + + + +«interface» +ConsoleOutputInterface + ++ «static» NO_FILE_TO_LINT : string = "Could\ not\ find\ any\ files\ to\ lint" {readOnly} + + ++ «abstract» createProgressBar(max = 0) : Symfony\Component\Console\Helper\ProgressBar ++ «abstract» progressStart(max : int = 0) : void ++ «abstract» progressAdvance(step : int = 1) : void ++ «abstract» progressFinish() : void ++ «abstract» progressMessage(message : string, name : string = 'message') : void ++ «abstract» progressPrinterAdvance(maxSteps : int, status : string, fileInfo : Symfony\Component\Finder\SplFileInfo, step : int = 1) : void ++ «abstract» headerBlock(appVersion : string, configFile : string) : void ++ «abstract» configBlock(options : array) : void ++ «abstract» consumeBlock(timeUsage : string, memUsage : string, cacheUsage : string, processCount : int) : void ++ «abstract» errorBlock(fileCount : int, errorCount : int) : void ++ «abstract» successBlock(fileCount : int) : void ++ «abstract» warningBlock(message : string = NO_FILE_TO_LINT) : void ++ «abstract» newLine(count : int = 1) : void + + + +Overtrue\\PHPLint\\Output\\ConsoleOutput->Overtrue\\PHPLint\\Output\\ConsoleOutputInterface + + - + Symfony\\Component\\Console\\Output\\ConsoleOutput - - - -ConsoleOutput - - -+ __construct(verbosity : int = VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») -+ section() : Symfony\Component\Console\Output\ConsoleSectionOutput -+ setDecorated(decorated : bool) -+ setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) -+ setVerbosity(level : int) -+ getErrorOutput() : Symfony\Component\Console\Output\OutputInterface -+ setErrorOutput(error : Symfony\Component\Console\Output\OutputInterface) + + + +ConsoleOutput + + ++ __construct(verbosity : int = VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») ++ section() : Symfony\Component\Console\Output\ConsoleSectionOutput ++ setDecorated(decorated : bool) ++ setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) ++ setVerbosity(level : int) ++ getErrorOutput() : Symfony\Component\Console\Output\OutputInterface ++ setErrorOutput(error : Symfony\Component\Console\Output\OutputInterface) Overtrue\\PHPLint\\Output\\ConsoleOutput->Symfony\\Component\\Console\\Output\\ConsoleOutput - - + + + + + +Overtrue\\PHPLint\\Output\\ConsoleOutputInterface->Overtrue\\PHPLint\\Output\\OutputInterface + + Overtrue\\PHPLint\\Output\\JsonOutput - - - -JsonOutput - - -+ format(results : Overtrue\PHPLint\Output\LinterOutput) : void + + + +JsonOutput + + ++ format(results : Overtrue\PHPLint\Output\LinterOutput) : void - + Overtrue\\PHPLint\\Output\\JsonOutput->Overtrue\\PHPLint\\Output\\OutputInterface - - + + - + Symfony\\Component\\Console\\Output\\StreamOutput - - - -StreamOutput - - -+ __construct(stream : resource, verbosity : int = VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») -+ getStream() + + + +StreamOutput + + ++ __construct(stream : resource, verbosity : int = VERBOSITY_NORMAL, decorated : bool = «unknown», formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») ++ getStream() - + Overtrue\\PHPLint\\Output\\JsonOutput->Symfony\\Component\\Console\\Output\\StreamOutput - - + + Overtrue\\PHPLint\\Output\\JunitOutput - - - -JunitOutput - - -+ format(results : Overtrue\PHPLint\Output\LinterOutput) : void + + + +JunitOutput + + ++ format(results : Overtrue\PHPLint\Output\LinterOutput) : void - + Overtrue\\PHPLint\\Output\\JunitOutput->Overtrue\\PHPLint\\Output\\OutputInterface - - + + - + Overtrue\\PHPLint\\Output\\JunitOutput->Symfony\\Component\\Console\\Output\\StreamOutput - - + + + + + +Overtrue\\PHPLint\\Output\\LinterOutput + + + +LinterOutput + + ++ __construct(results : array, finder : Symfony\Component\Finder\Finder) ++ count() : int ++ getContext() : array ++ setContext(configResolver : Overtrue\PHPLint\Configuration\Resolver, startTime : float, processCount : int) : void ++ hasFailures() : bool ++ getFailures() : array ++ hasErrors() : bool ++ getErrors() : array ++ hasWarnings() : bool ++ getWarnings() : array ++ getHits() : array ++ getMisses() : array + + + +Countable + + + +«interface» +Countable + + ++ «abstract» count() + + + +Overtrue\\PHPLint\\Output\\LinterOutput->Countable + + Symfony\\Component\\Console\\Output\\ConsoleOutput->Symfony\\Component\\Console\\Output\\StreamOutput - - + + - + Symfony\\Component\\Console\\Output\\ConsoleOutputInterface - - - -«interface» -ConsoleOutputInterface - -+ «static» VERBOSITY_QUIET : int = 16 {readOnly} -+ «static» VERBOSITY_NORMAL : int = 32 {readOnly} -+ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} -+ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} -+ «static» VERBOSITY_DEBUG : int = 256 {readOnly} -+ «static» OUTPUT_NORMAL : int = 1 {readOnly} -+ «static» OUTPUT_RAW : int = 2 {readOnly} -+ «static» OUTPUT_PLAIN : int = 4 {readOnly} - - -+ «abstract» getErrorOutput() : Symfony\Component\Console\Output\OutputInterface -+ «abstract» setErrorOutput(error : Symfony\Component\Console\Output\OutputInterface) -+ «abstract» section() : Symfony\Component\Console\Output\ConsoleSectionOutput + + + +«interface» +ConsoleOutputInterface + ++ «static» VERBOSITY_QUIET : int = 16 {readOnly} ++ «static» VERBOSITY_NORMAL : int = 32 {readOnly} ++ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} ++ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} ++ «static» VERBOSITY_DEBUG : int = 256 {readOnly} ++ «static» OUTPUT_NORMAL : int = 1 {readOnly} ++ «static» OUTPUT_RAW : int = 2 {readOnly} ++ «static» OUTPUT_PLAIN : int = 4 {readOnly} + + ++ «abstract» getErrorOutput() : Symfony\Component\Console\Output\OutputInterface ++ «abstract» setErrorOutput(error : Symfony\Component\Console\Output\OutputInterface) ++ «abstract» section() : Symfony\Component\Console\Output\ConsoleSectionOutput Symfony\\Component\\Console\\Output\\ConsoleOutput->Symfony\\Component\\Console\\Output\\ConsoleOutputInterface - - + + - + Symfony\\Component\\Console\\Output\\Output - - - -«abstract» -Output - -+ «static» VERBOSITY_QUIET : int = 16 {readOnly} -+ «static» VERBOSITY_NORMAL : int = 32 {readOnly} -+ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} -+ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} -+ «static» VERBOSITY_DEBUG : int = 256 {readOnly} -+ «static» OUTPUT_NORMAL : int = 1 {readOnly} -+ «static» OUTPUT_RAW : int = 2 {readOnly} -+ «static» OUTPUT_PLAIN : int = 4 {readOnly} - - -+ __construct(verbosity : int = VERBOSITY_NORMAL, decorated : bool = false, formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») -+ setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) -+ getFormatter() : Symfony\Component\Console\Formatter\OutputFormatterInterface -+ setDecorated(decorated : bool) -+ isDecorated() : bool -+ setVerbosity(level : int) -+ getVerbosity() : int -+ isQuiet() : bool -+ isVerbose() : bool -+ isVeryVerbose() : bool -+ isDebug() : bool -+ writeln(messages, options : int = OUTPUT_NORMAL) -+ write(messages, newline : bool = false, options : int = OUTPUT_NORMAL) + + + +«abstract» +Output + ++ «static» VERBOSITY_QUIET : int = 16 {readOnly} ++ «static» VERBOSITY_NORMAL : int = 32 {readOnly} ++ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} ++ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} ++ «static» VERBOSITY_DEBUG : int = 256 {readOnly} ++ «static» OUTPUT_NORMAL : int = 1 {readOnly} ++ «static» OUTPUT_RAW : int = 2 {readOnly} ++ «static» OUTPUT_PLAIN : int = 4 {readOnly} + + ++ __construct(verbosity : int = VERBOSITY_NORMAL, decorated : bool = false, formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface = «unknown») ++ setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) ++ getFormatter() : Symfony\Component\Console\Formatter\OutputFormatterInterface ++ setDecorated(decorated : bool) ++ isDecorated() : bool ++ setVerbosity(level : int) ++ getVerbosity() : int ++ isQuiet() : bool ++ isVerbose() : bool ++ isVeryVerbose() : bool ++ isDebug() : bool ++ writeln(messages, options : int = OUTPUT_NORMAL) ++ write(messages, newline : bool = false, options : int = OUTPUT_NORMAL) Symfony\\Component\\Console\\Output\\StreamOutput->Symfony\\Component\\Console\\Output\\Output - - + + - + Symfony\\Component\\Console\\Output\\OutputInterface - - - -«interface» -OutputInterface - -+ «static» VERBOSITY_QUIET : int = 16 {readOnly} -+ «static» VERBOSITY_NORMAL : int = 32 {readOnly} -+ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} -+ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} -+ «static» VERBOSITY_DEBUG : int = 256 {readOnly} -+ «static» OUTPUT_NORMAL : int = 1 {readOnly} -+ «static» OUTPUT_RAW : int = 2 {readOnly} -+ «static» OUTPUT_PLAIN : int = 4 {readOnly} - - -+ «abstract» write(messages, newline : bool = false, options : int = 0) -+ «abstract» writeln(messages, options : int = 0) -+ «abstract» setVerbosity(level : int) -+ «abstract» getVerbosity() : int -+ «abstract» isQuiet() : bool -+ «abstract» isVerbose() : bool -+ «abstract» isVeryVerbose() : bool -+ «abstract» isDebug() : bool -+ «abstract» setDecorated(decorated : bool) -+ «abstract» isDecorated() : bool -+ «abstract» setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) -+ «abstract» getFormatter() : Symfony\Component\Console\Formatter\OutputFormatterInterface + + + +«interface» +OutputInterface + ++ «static» VERBOSITY_QUIET : int = 16 {readOnly} ++ «static» VERBOSITY_NORMAL : int = 32 {readOnly} ++ «static» VERBOSITY_VERBOSE : int = 64 {readOnly} ++ «static» VERBOSITY_VERY_VERBOSE : int = 128 {readOnly} ++ «static» VERBOSITY_DEBUG : int = 256 {readOnly} ++ «static» OUTPUT_NORMAL : int = 1 {readOnly} ++ «static» OUTPUT_RAW : int = 2 {readOnly} ++ «static» OUTPUT_PLAIN : int = 4 {readOnly} + + ++ «abstract» write(messages, newline : bool = false, options : int = 0) ++ «abstract» writeln(messages, options : int = 0) ++ «abstract» setVerbosity(level : int) ++ «abstract» getVerbosity() : int ++ «abstract» isQuiet() : bool ++ «abstract» isVerbose() : bool ++ «abstract» isVeryVerbose() : bool ++ «abstract» isDebug() : bool ++ «abstract» setDecorated(decorated : bool) ++ «abstract» isDecorated() : bool ++ «abstract» setFormatter(formatter : Symfony\Component\Console\Formatter\OutputFormatterInterface) ++ «abstract» getFormatter() : Symfony\Component\Console\Formatter\OutputFormatterInterface Symfony\\Component\\Console\\Output\\Output->Symfony\\Component\\Console\\Output\\OutputInterface - - + + Symfony\\Component\\Console\\Output\\ConsoleOutputInterface->Symfony\\Component\\Console\\Output\\OutputInterface - - + + diff --git a/docs/assets/process-uml-diagram.svg b/docs/assets/process-uml-diagram.svg index f7da112e..01ae617e 100644 --- a/docs/assets/process-uml-diagram.svg +++ b/docs/assets/process-uml-diagram.svg @@ -1,178 +1,185 @@ - - - + + %3 cluster_0 - -Overtrue\PHPLint\Process + +Overtrue\PHPLint\Process cluster_1 - -Symfony\Component\Process + +Symfony\Component\Process cluster_2 - -0 - - - -Overtrue\\PHPLint\\Process\\LintProcessItem - - - -LintProcessItem - - -+ hasSyntaxError() : bool -+ hasSyntaxWarning() : bool -+ getMessage() : string -+ getLine() : int + +0 - + Overtrue\\PHPLint\\Process\\LintProcess - - - -LintProcess - - -+ __construct(command : array, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) -+ getItem(output : string) : Overtrue\PHPLint\Process\LintProcessItem + + + +LintProcess + + ++ __construct(command : array, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) ++ setHelper(helper : Symfony\Component\Console\Helper\HelperInterface) : self ++ setOutput(output : Symfony\Component\Console\Output\OutputInterface) : self ++ setFiles(files : array) : self ++ getFiles() : array ++ getItem(fileInfo : Symfony\Component\Finder\SplFileInfo) : Overtrue\PHPLint\Process\LintProcessItem ++ begin(callback : callable = «unknown», env : array = []) : void ++ isFinished() : bool Symfony\\Component\\Process\\Process - - - -Process - -+ «static» ERR : string = "err" {readOnly} -+ «static» OUT : string = "out" {readOnly} -+ «static» STATUS_READY : string = "ready" {readOnly} -+ «static» STATUS_STARTED : string = "started" {readOnly} -+ «static» STATUS_TERMINATED : string = "terminated" {readOnly} -+ «static» STDIN : int = 0 {readOnly} -+ «static» STDOUT : int = 1 {readOnly} -+ «static» STDERR : int = 2 {readOnly} -+ «static» TIMEOUT_PRECISION : float = 0.2 {readOnly} -+ «static» ITER_NON_BLOCKING : int = 1 {readOnly} -+ «static» ITER_KEEP_OUTPUT : int = 2 {readOnly} -+ «static» ITER_SKIP_OUT : int = 4 {readOnly} -+ «static» ITER_SKIP_ERR : int = 8 {readOnly} - -+ «static» exitCodes = […] - -+ __construct(command : array, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) -+ «static» fromShellCommandline(command : string, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) : static -+ __sleep() : array -+ __wakeup() -+ __destruct() -+ __clone() -+ run(callback : callable = «unknown», env : array = []) : int -+ mustRun(callback : callable = «unknown», env : array = []) : static -+ start(callback : callable = «unknown», env : array = []) -+ restart(callback : callable = «unknown», env : array = []) : static -+ wait(callback : callable = «unknown») : int -+ waitUntil(callback : callable) : bool -+ getPid() : ?int -+ signal(signal : int) : static -+ disableOutput() : static -+ enableOutput() : static -+ isOutputDisabled() : bool -+ getOutput() : string -+ getIncrementalOutput() : string -+ getIterator(flags : int = 0) : Generator -+ clearOutput() : static -+ getErrorOutput() : string -+ getIncrementalErrorOutput() : string -+ clearErrorOutput() : static -+ getExitCode() : ?int -+ getExitCodeText() : ?string -+ isSuccessful() : bool -+ hasBeenSignaled() : bool -+ getTermSignal() : int -+ hasBeenStopped() : bool -+ getStopSignal() : int -+ isRunning() : bool -+ isStarted() : bool -+ isTerminated() : bool -+ getStatus() : string -+ stop(timeout : float = 10, signal : int = «unknown») : ?int -+ addOutput(line : string) -+ addErrorOutput(line : string) -+ getLastOutputTime() : ?float -+ getCommandLine() : string -+ getTimeout() : ?float -+ getIdleTimeout() : ?float -+ setTimeout(timeout : float) : static -+ setIdleTimeout(timeout : float) : static -+ setTty(tty : bool) : static -+ isTty() : bool -+ setPty(bool : bool) : static -+ isPty() : bool -+ getWorkingDirectory() : ?string -+ setWorkingDirectory(cwd : string) : static -+ getEnv() : array -+ setEnv(env : array) : static -+ getInput() -+ setInput(input : mixed) : static -+ checkTimeout() -+ getStartTime() : float -+ setOptions(options : array) -+ «static» isTtySupported() : bool -+ «static» isPtySupported() : bool + + + +Process + ++ «static» ERR : string = "err" {readOnly} ++ «static» OUT : string = "out" {readOnly} ++ «static» STATUS_READY : string = "ready" {readOnly} ++ «static» STATUS_STARTED : string = "started" {readOnly} ++ «static» STATUS_TERMINATED : string = "terminated" {readOnly} ++ «static» STDIN : int = 0 {readOnly} ++ «static» STDOUT : int = 1 {readOnly} ++ «static» STDERR : int = 2 {readOnly} ++ «static» TIMEOUT_PRECISION : float = 0.2 {readOnly} ++ «static» ITER_NON_BLOCKING : int = 1 {readOnly} ++ «static» ITER_KEEP_OUTPUT : int = 2 {readOnly} ++ «static» ITER_SKIP_OUT : int = 4 {readOnly} ++ «static» ITER_SKIP_ERR : int = 8 {readOnly} + ++ «static» exitCodes = […] + ++ __construct(command : array, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) ++ «static» fromShellCommandline(command : string, cwd : string = «unknown», env : array = «unknown», input : mixed = «unknown», timeout : float = 60) : static ++ __sleep() : array ++ __wakeup() ++ __destruct() ++ __clone() ++ run(callback : callable = «unknown», env : array = []) : int ++ mustRun(callback : callable = «unknown», env : array = []) : static ++ start(callback : callable = «unknown», env : array = []) ++ restart(callback : callable = «unknown», env : array = []) : static ++ wait(callback : callable = «unknown») : int ++ waitUntil(callback : callable) : bool ++ getPid() : ?int ++ signal(signal : int) : static ++ disableOutput() : static ++ enableOutput() : static ++ isOutputDisabled() : bool ++ getOutput() : string ++ getIncrementalOutput() : string ++ getIterator(flags : int = 0) : Generator ++ clearOutput() : static ++ getErrorOutput() : string ++ getIncrementalErrorOutput() : string ++ clearErrorOutput() : static ++ getExitCode() : ?int ++ getExitCodeText() : ?string ++ isSuccessful() : bool ++ hasBeenSignaled() : bool ++ getTermSignal() : int ++ hasBeenStopped() : bool ++ getStopSignal() : int ++ isRunning() : bool ++ isStarted() : bool ++ isTerminated() : bool ++ getStatus() : string ++ stop(timeout : float = 10, signal : int = «unknown») : ?int ++ addOutput(line : string) : void ++ addErrorOutput(line : string) : void ++ getLastOutputTime() : ?float ++ getCommandLine() : string ++ getTimeout() : ?float ++ getIdleTimeout() : ?float ++ setTimeout(timeout : float) : static ++ setIdleTimeout(timeout : float) : static ++ setTty(tty : bool) : static ++ isTty() : bool ++ setPty(bool : bool) : static ++ isPty() : bool ++ getWorkingDirectory() : ?string ++ setWorkingDirectory(cwd : string) : static ++ getEnv() : array ++ setEnv(env : array) : static ++ getInput() ++ setInput(input : mixed) : static ++ checkTimeout() ++ getStartTime() : float ++ setOptions(options : array) ++ «static» isTtySupported() : bool ++ «static» isPtySupported() : bool Overtrue\\PHPLint\\Process\\LintProcess->Symfony\\Component\\Process\\Process - - + + + + + +Overtrue\\PHPLint\\Process\\LintProcessItem + + + +LintProcessItem + + ++ hasSyntaxError() : bool ++ hasSyntaxWarning() : bool ++ getMessage() : string ++ getLine() : int ++ getFileInfo() : Symfony\Component\Finder\SplFileInfo IteratorAggregate - - - -«interface» -IteratorAggregate - - -+ «abstract» getIterator() + + + +«interface» +IteratorAggregate + + ++ «abstract» getIterator() Symfony\\Component\\Process\\Process->IteratorAggregate - - + + Traversable - - - -«interface» -Traversable - - + + + +«interface» +Traversable + + IteratorAggregate->Traversable - - + + From 87fce46ab32076637779bb69d0bcde495c891d4a Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Mon, 5 Feb 2024 06:31:54 +0000 Subject: [PATCH 50/53] fix issue #200 --- .changes/unreleased/Fixed-20240205-063044.yaml | 4 ++++ entrypoint.sh | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 .changes/unreleased/Fixed-20240205-063044.yaml diff --git a/.changes/unreleased/Fixed-20240205-063044.yaml b/.changes/unreleased/Fixed-20240205-063044.yaml new file mode 100644 index 00000000..193840c5 --- /dev/null +++ b/.changes/unreleased/Fixed-20240205-063044.yaml @@ -0,0 +1,4 @@ +kind: Fixed +body: '[#200](https://github.com/overtrue/phplint/issues/200) : Unable to create file + /github/home/.composer/config.json in Github Action' +time: 2024-02-05T06:30:44.244619077Z diff --git a/entrypoint.sh b/entrypoint.sh index b4e3c22d..8e20d93b 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -6,8 +6,8 @@ set -e if [ "$APP_DEBUG" == 'true' ] then echo "> You will act as user: $(id -u -n)" - echo "$(composer config --global --list)" - /bin/sh -c "ls -l $(composer config --global home)" + composer_global_home="/home/$(id -u -n)/.composer" + echo "> Path to Composer home dir: ${composer_global_home}" fi -"$(composer config --global home)/vendor/bin/phplint" $@ +"${composer_global_home}/vendor/bin/phplint" $@ From a0407dc14176449693d8edeee51148c4805c241a Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Mon, 5 Feb 2024 06:39:17 +0000 Subject: [PATCH 51/53] fix issue #201 --- .changes/unreleased/Fixed-20240205-063850.yaml | 4 ++++ Dockerfile | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/Fixed-20240205-063850.yaml diff --git a/.changes/unreleased/Fixed-20240205-063850.yaml b/.changes/unreleased/Fixed-20240205-063850.yaml new file mode 100644 index 00000000..89154a4b --- /dev/null +++ b/.changes/unreleased/Fixed-20240205-063850.yaml @@ -0,0 +1,4 @@ +kind: Fixed +body: '[#201](https://github.com/overtrue/phplint/issues/201) : GitHub Action build + docker on fly with wrong version' +time: 2024-02-05T06:38:50.002039814Z diff --git a/Dockerfile b/Dockerfile index 7d746abe..83f9deae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1.4 ARG PHP_VERSION=8.2 +ARG PHPLINT_VERSION=9.0.7 FROM php:${PHP_VERSION}-cli-alpine @@ -16,7 +17,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.0 +RUN composer global require --no-progress overtrue/phplint ${PHPLINT_VERSION} # Following recommendation at https://docs.github.com/en/actions/creating-actions/dockerfile-support-for-github-actions#workdir From a0f1addcd056f601054e6f7fe3a495c28eb1c0ed Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Mon, 5 Feb 2024 08:46:14 +0000 Subject: [PATCH 52/53] bump new version (of branch 9.0) --- src/Console/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Application.php b/src/Console/Application.php index 5fe73826..13396716 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -33,7 +33,7 @@ final class Application extends BaseApplication { public const NAME = 'phplint'; - public const VERSION = '9.0.6'; + public const VERSION = '9.0.7'; public function __construct() { From 4eb6326ed46ee3488b992d09a1e3d691167fc45d Mon Sep 17 00:00:00 2001 From: Laurent Laville Date: Mon, 5 Feb 2024 08:52:42 +0000 Subject: [PATCH 53/53] prepare bugfix release 9.0.7 --- .changes/9.0.7.md | 9 +++++++++ .changes/unreleased/Fixed-20240205-063044.yaml | 4 ---- .changes/unreleased/Fixed-20240205-063850.yaml | 4 ---- CHANGELOG.md | 9 +++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .changes/9.0.7.md delete mode 100644 .changes/unreleased/Fixed-20240205-063044.yaml delete mode 100644 .changes/unreleased/Fixed-20240205-063850.yaml diff --git a/.changes/9.0.7.md b/.changes/9.0.7.md new file mode 100644 index 00000000..d8653f87 --- /dev/null +++ b/.changes/9.0.7.md @@ -0,0 +1,9 @@ + +## 9.0.7 - 2024-02-05 + +### Fixed + +- [#200](https://github.com/overtrue/phplint/issues/200) : Unable to create file `/github/home/.composer/config.json` in GitHub Action +- [#201](https://github.com/overtrue/phplint/issues/201) : GitHub Action build docker on fly with wrong version + +**Full Changelog**: [9.0.6...9.0.7](https://github.com/overtrue/phplint/compare/9.0.6...9.0.7) diff --git a/.changes/unreleased/Fixed-20240205-063044.yaml b/.changes/unreleased/Fixed-20240205-063044.yaml deleted file mode 100644 index 193840c5..00000000 --- a/.changes/unreleased/Fixed-20240205-063044.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Fixed -body: '[#200](https://github.com/overtrue/phplint/issues/200) : Unable to create file - /github/home/.composer/config.json in Github Action' -time: 2024-02-05T06:30:44.244619077Z diff --git a/.changes/unreleased/Fixed-20240205-063850.yaml b/.changes/unreleased/Fixed-20240205-063850.yaml deleted file mode 100644 index 89154a4b..00000000 --- a/.changes/unreleased/Fixed-20240205-063850.yaml +++ /dev/null @@ -1,4 +0,0 @@ -kind: Fixed -body: '[#201](https://github.com/overtrue/phplint/issues/201) : GitHub Action build - docker on fly with wrong version' -time: 2024-02-05T06:38:50.002039814Z diff --git a/CHANGELOG.md b/CHANGELOG.md index f900fed4..62cfe84f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## 9.0.7 - 2024-02-05 + +### Fixed + +- [#200](https://github.com/overtrue/phplint/issues/200) : Unable to create file `/github/home/.composer/config.json` in GitHub Action +- [#201](https://github.com/overtrue/phplint/issues/201) : GitHub Action build docker on fly with wrong version + +**Full Changelog**: [9.0.6...9.0.7](https://github.com/overtrue/phplint/compare/9.0.6...9.0.7) + ## 9.0.6 - 2023-12-02 ### Fixed