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/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/.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" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 926e1569..990ce4da 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,20 +29,12 @@ 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 }}" + coverage: "none" tools: phpunit:10.5 - # https://github.com/ramsey/composer-install @@ -52,11 +43,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/.github/workflows/release.yml b/.github/workflows/release.yml index 1bfb4bf8..ef587203 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 @@ -34,8 +34,14 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + 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/.gitignore b/.gitignore index 4ea98549..1e07ad01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ composer.lock /vendor/ .phpunit.cache/ -.phpunit.result.cache .phplint.cache/ .idea .php-cs-fixer.cache -/vendor-bin/**/vendor \ No newline at end of file +/vendor-bin/**/vendor +.phpbench/ +site/ \ No newline at end of file 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/CHANGELOG.md b/CHANGELOG.md index f900fed4..8ad14149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,42 @@ 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.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/Dockerfile b/Dockerfile index 7d746abe..3907ed45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # syntax=docker/dockerfile:1.4 -ARG PHP_VERSION=8.2 +ARG PHP_VERSION=8.0 +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 diff --git a/README.md b/README.md index 01d20af8..4722b094 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,19 @@ | 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. +with a lot of improvement, full documentation, and full unit code tested. ## Table of Contents diff --git a/bin/phplint b/bin/phplint index b97cc9c8..b17c66a7 100755 --- a/bin/phplint +++ b/bin/phplint @@ -1,27 +1,9 @@ #!/usr/bin/env php 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/box.json b/box.json.dist similarity index 93% rename from box.json rename to box.json.dist index 368bd6e8..60d7afc0 100644 --- a/box.json +++ b/box.json.dist @@ -1,4 +1,5 @@ { + "compression": "GZ", "chmod": "0700", "banner": [ "This file is part of the overtrue/phplint package", diff --git a/composer.json b/composer.json index a6c05732..0c277959 100644 --- a/composer.json +++ b/composer.json @@ -21,21 +21,22 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", + "ext-dom": "*", "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", "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": { @@ -51,11 +52,15 @@ "extra": { "hooks": { "pre-commit": [ - "composer fix-style" + "composer style:fix", + "composer code:check" + ], + "pre-push": [ + "composer qa:check" ] }, "branch-alias": { - "dev-main": "9.0.x-dev" + "dev-main": "9.1.x-dev" } }, "scripts": { @@ -71,18 +76,28 @@ "@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", + "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", "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, "scripts-descriptions": { - "check-style": "Run style checks (only dry run - no fixing!).", - "fix-style": "Run style checks and fix violations." + "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", + "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", + "code:check": "Run PHPStan code analysis on PHPLint source code" }, "bin": [ "bin/phplint" diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 00000000..e722415d --- /dev/null +++ b/config/bootstrap.php @@ -0,0 +1,51 @@ +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/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 2c87a609..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 @@ -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 + - - - /path/to/tests/fixtures/syntax_error.php + + + /path/to/fixtures/syntax_error.php + /path/to/fixtures/php-8.2_syntax.php ``` -[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-output-example.png b/docs/assets/console-output-example.png index acbd42b6..6aec64e8 100644 Binary files a/docs/assets/console-output-example.png and b/docs/assets/console-output-example.png differ 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/phpstan_run.png b/docs/assets/phpstan_run.png new file mode 100644 index 00000000..593b9fbd Binary files /dev/null and b/docs/assets/phpstan_run.png differ diff --git a/docs/assets/pre-push-hook.png b/docs/assets/pre-push-hook.png new file mode 100644 index 00000000..34e24706 Binary files /dev/null and b/docs/assets/pre-push-hook.png differ 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 - - + + diff --git a/docs/assets/progress-indicator-finished.png b/docs/assets/progress-indicator-finished.png new file mode 100644 index 00000000..9257bda3 Binary files /dev/null and b/docs/assets/progress-indicator-finished.png differ diff --git a/docs/assets/progress-indicator-running.png b/docs/assets/progress-indicator-running.png new file mode 100644 index 00000000..f8180410 Binary files /dev/null and b/docs/assets/progress-indicator-running.png differ diff --git a/docs/contributing.md b/docs/contributing.md index af51eb2f..9fe83149 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. @@ -26,25 +35,40 @@ composer update ``` In an effort to maintain a homogeneous code base, we strongly encourage contributors to run -[PHP-CS-Fixer][php-cs-fixer] and [PHPUnit][phpunit] before submitting a Pull Request. +[PHPStan][phpstan], [PHP-CS-Fixer][php-cs-fixer] and [PHPUnit][phpunit] before submitting a Pull Request. + +All dev tools (`phpstan`, `php-cs-fixer`, `phpunit`) are under control of [bamarni/composer-bin-plugin][bamarni/composer-bin-plugin]. + +## Static Code Analysis + +Static analysis of source code is provided using [PHPStan][phpstan] + +This project comes with a configuration file (located at `/phpstan.neon.dist` in the repository) +and an executable for PHPStan (located at `vendor/bin/phpstan`) that you can use to analyse your source code for compliance with this project's coding guidelines: + +```shell +composer code:check +``` + +Here is a preview of what call look like: -All dev tools (`php-cs-fixer`, `phpunit`) are under control of [bamarni/composer-bin-plugin][bamarni/composer-bin-plugin]. +![phpstan_run](./assets/phpstan_run.png) ## Coding standards 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: @@ -73,5 +97,6 @@ composer tests:all Execute all tests (unit and end-to-end) [bamarni/composer-bin-plugin]: https://github.com/bamarni/composer-bin-plugin +[phpstan]: https://github.com/phpstan/phpstan [php-cs-fixer]: https://github.com/PHP-CS-Fixer/PHP-CS-Fixer [phpunit]: https://github.com/sebastianbergmann/phpunit 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/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 ! 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: 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" $@ 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)); 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/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..6e2cadb8 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,4 @@ +parameters: + level: 5 + paths: + - src/ 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..8f1685b2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ /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 00000000..fe55587c Binary files /dev/null and b/resources/graph-uml.phar differ diff --git a/resources/graph-uml/build.php b/resources/graph-uml/build.php new file mode 100644 index 00000000..5cb1829d --- /dev/null +++ b/resources/graph-uml/build.php @@ -0,0 +1,88 @@ +createVerticesFromCallable($callback, 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 @@ +pool->hasItem($this->getKey($filename)); } + /** + * @throws InvalidArgumentException + */ public function getItem(string $filename): CacheItemInterface { return $this->pool->getItem($this->getKey($filename)); @@ -99,9 +102,15 @@ public function saveItem(CacheItemInterface $item): bool public function clear(string $prefix = ''): bool { - return $this->pool->clear($prefix); + if ($this->pool instanceof AdapterInterface) { + return $this->pool->clear($prefix); + } + return $this->pool->clear(); } + /** + * @throws InvalidArgumentException + */ public function isHit(string $filename): bool { // Try to fetch item from cache @@ -125,7 +134,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 diff --git a/src/Command/LintCommand.php b/src/Command/LintCommand.php index 1bb62255..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; @@ -74,7 +73,7 @@ protected function initialize(InputInterface $input, OutputInterface $output): v } /** - * @throws InvalidStyleException|Throwable + * @throws Throwable */ protected function execute(InputInterface $input, OutputInterface $output): int { @@ -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/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 = []; } 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/Console/Application.php b/src/Console/Application.php index 5fe73826..50b6b201 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; @@ -33,7 +36,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() { @@ -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/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/Extension/ProgressBar.php b/src/Extension/ProgressBar.php index 23315474..553ee679 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,8 @@ final class ProgressBar implements BeforeLintFileInterface, AfterLintFileInterface { - private OutputInterface $output; + private ConsoleOutputInterface $output; + private bool $hasProcessHelper; public static function getSubscribedEvents(): array { @@ -55,18 +59,30 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { - $this->output = $event->getOutput(); + $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; } 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')); + // @phpstan-ignore-next-line + 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')); } @@ -86,6 +102,12 @@ public function beforeLintFile(BeforeLintFileEvent $event): void public function afterLintFile(AfterLintFileEvent $event): void { + // @phpstan-ignore-next-line + if ($this->hasProcessHelper && $this->output->isVeryVerbose()) { + // ProgressBar extension make some noise that break output when ProcessHelper is active + return; + } + $this->output->progressAdvance(); } } 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(); + } +} diff --git a/src/Extension/ProgressPrinter.php b/src/Extension/ProgressPrinter.php index 477aa3d7..ee0fa6e5 100644 --- a/src/Extension/ProgressPrinter.php +++ b/src/Extension/ProgressPrinter.php @@ -13,15 +13,20 @@ 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 * @since Release 9.0.0 @@ -31,9 +36,10 @@ final class ProgressPrinter implements BeforeCheckingInterface, AfterLintFileInterface { - private OutputInterface $output; + private ConsoleOutputInterface $output; private int $maxSteps = 0; + private bool $hasProcessHelper; public static function getSubscribedEvents(): array { @@ -47,24 +53,36 @@ public static function getSubscribedEvents(): array */ public function initProgress(ConsoleCommandEvent $event): void { - $this->output = $event->getOutput(); + $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; } 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/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); } diff --git a/src/Helper/DebugFormatterHelper.php b/src/Helper/DebugFormatterHelper.php new file mode 100644 index 00000000..a549bd31 --- /dev/null +++ b/src/Helper/DebugFormatterHelper.php @@ -0,0 +1,104 @@ +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), $errorPrefix); + + 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']]); + } +} 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); + } +} diff --git a/src/Linter.php b/src/Linter.php index 5dfe03a3..c5f82b51 100644 --- a/src/Linter.php +++ b/src/Linter.php @@ -20,18 +20,27 @@ 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 Psr\Cache\InvalidArgumentException; 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 count; use function md5_file; use function microtime; -use function trim; +use function phpversion; +use function version_compare; /** * @author Overtrue @@ -42,6 +51,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; @@ -49,8 +60,13 @@ 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', + HelperSet $helperSet = null, + OutputInterface $output = null + ) { $this->configResolver = $configResolver; $this->dispatcher = $dispatcher; $this->appLongVersion = $appVersion; @@ -64,8 +80,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' => [], @@ -73,8 +88,14 @@ public function __construct(Resolver $configResolver, EventDispatcherInterface $ 'hits' => [], 'misses' => [], ]; + + $this->helperSet = $helperSet; + $this->output = $output; } + /** + * @throws Throwable + */ public function lintFiles(Finder $finder, ?float $startTime = null): LinterOutput { if (null === $startTime) { @@ -87,6 +108,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, @@ -94,67 +124,105 @@ 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) { - $fileInfo = $iterator->current(); - $this->dispatcher->dispatch(new BeforeLintFileEvent($this, ['file' => $fileInfo])); - $filename = $fileInfo->getRealPath(); - - if ($this->cache->isHit($filename)) { - $this->results['hits'][] = $filename; - } else { - $lintProcess = $this->createLintProcess($filename); - $lintProcess->start(); - - ++$pid; - $processRunning[$pid] = [ - 'process' => $lintProcess, - 'file' => $fileInfo, - ]; - $this->results['misses'][] = $filename; - } - - $iterator->next(); - } - - foreach ($processRunning as $pid => $item) { - /** @var LintProcess $lintProcess */ - $lintProcess = $item['process']; - if ($lintProcess->isRunning()) { - 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])); - } - } - - $results = $this->results; + $results = $this->doLint($finder, $processCount); } else { $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])); return $finalResults; } + /** + * @throws InvalidArgumentException + */ + 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 + * @throws InvalidArgumentException + */ + 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]) + ); + } + } + } + + /** + * @throws InvalidArgumentException + */ private function processFile(SplFileInfo $fileInfo, LintProcess $lintProcess): string { $filename = $fileInfo->getRealPath(); - $output = trim($lintProcess->getOutput()); - $item = $lintProcess->getItem($output); + $item = $lintProcess->getItem($fileInfo); if ($item->hasSyntaxError()) { $status = 'error'; @@ -171,7 +239,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(), ]; @@ -180,7 +248,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', @@ -193,8 +261,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 ae9b69f0..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; @@ -58,17 +56,13 @@ * @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; - private Resolver $configResolver; - private string $appVersion; public function __construct(int $verbosity = parent::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null) { @@ -77,32 +71,11 @@ 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(); $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) { @@ -110,7 +83,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); @@ -173,7 +146,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; @@ -218,7 +191,8 @@ public function progressPrinterAdvance(int $maxSteps, string $status, SplFileInf $this->newLine(); } } - ++$i; + + $i += $step; } public function headerBlock(string $appVersion, string $configFile): void @@ -300,13 +274,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 new file mode 100644 index 00000000..41d38b98 --- /dev/null +++ b/src/Output/ConsoleOutputInterface.php @@ -0,0 +1,52 @@ +getFailures(); 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 336fe07c..28fe3c06 100644 --- a/src/Process/LintProcess.php +++ b/src/Process/LintProcess.php @@ -14,13 +14,18 @@ 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; -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,14 +33,23 @@ */ 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) { + 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 +63,7 @@ static function (bool $hasError, string $errorString, int $errorLine, bool $hasW $item->message = ''; $item->line = 0; } + $item->fileInfo = $fileInfo; return $item; }, null, @@ -56,44 +71,107 @@ static function (bool $hasError, string $errorString, int $errorLine, bool $hasW ); } - public function getItem(string $output): LintProcessItem + public function setHelper(?HelperInterface $helper): self { - $hasError = !str_contains($output, 'No syntax errors detected'); - $hasWarning = (bool) preg_match('/(Warning:|Deprecated:|Notice:)/', $output); + if ($helper instanceof ProcessHelper) { + $this->helper = $helper; + } else { + $this->helper = null; + } + return $this; + } - $out = explode("\n", trim($output)); - $text = array_shift($out); + public function setOutput(?OutputInterface $output): self + { + $this->output = $output; + return $this; + } - if ($hasError) { - $pattern = '/^(PHP\s+)?(Parse|Fatal) error:\s*(?:\w+ error,\s*)?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; + public function setFiles(array $files): self + { + $this->files = $files; + return $this; + } - $matched = preg_match($pattern, $text, $match); + public function getFiles(): array + { + return $this->files; + } + + public function getItem(SplFileInfo $fileInfo): LintProcessItem + { + $filename = $fileInfo->getRelativePathname(); - if (empty($message)) { - $message = 'Unknown'; + $messages = preg_split('/\n/', $this->getOutput(), -1, PREG_SPLIT_NO_EMPTY); + + $filtered = array_filter($messages, function ($message) use ($filename) { + return str_contains($message, $filename); + }); + + $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 = []; + + if ($hasError) { + $pattern = '/^(PHP\s+)?(Parse|Fatal) error:\s*(?:\w+ error,\s*)?(?.+?)\s+in\s+.+?\s*line\s+(?\d+)/'; + + $matched = preg_match($pattern, $message, $match); } else { - $message = ''; $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 = []; + 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)) { - $message = 'Unknown'; - } + $matched = preg_match($pattern, $message, $match); } else { - $message = ''; $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); + } + + 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/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; + } } 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(); 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; + } +} diff --git a/vendor-bin/phpstan/composer.json b/vendor-bin/phpstan/composer.json new file mode 100644 index 00000000..e452dbff --- /dev/null +++ b/vendor-bin/phpstan/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "phpstan/phpstan": "^1.10" + } +} diff --git a/vendor-bin/phpunit/composer.json b/vendor-bin/phpunit/composer.json index 5030a0e3..f4727287 100644 --- a/vendor-bin/phpunit/composer.json +++ b/vendor-bin/phpunit/composer.json @@ -1,5 +1,5 @@ { "require-dev": { - "phpunit/phpunit": "^9.6 || ^10.0" + "phpunit/phpunit": "^10.0" } }