diff --git a/.gitattributes b/.gitattributes index 61c19cd..319afb0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,13 +6,13 @@ # https://blog.madewithlove.be/post/gitattributes/ # /.gitattributes export-ignore +/.github export-ignore /.gitignore export-ignore -/.travis.yml export-ignore +/.github/ export-ignore /appveyor.yml export-ignore /box.json export-ignore /doc export-ignore /phpcs.xml.dist export-ignore -/phpcs-ruleset.xml export-ignore /tests export-ignore # diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e1e1cd8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,119 @@ +name: Build and test phar + +on: + push: + tags: + - 'v*' + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + bundle: + name: Bundle binary + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 5.4 + extensions: exif, phar, openssl + coverage: none + ini-values: phar.readonly=Off + + - name: Install Box from GitHub + run: | + curl -LSs https://box-project.github.io/box2/installer.php | php + test -f ./box.phar + test -d ~/bin || mkdir ~/bin + mv ./box.phar ~/bin/box + ~/bin/box -V + echo "$HOME/bin" >> $GITHUB_PATH + + - name: Install Composer dependencies + uses: ramsey/composer-install@v1 + with: + composer-options: "--no-dev" + + - name: Building binary... + run: box build -v + + - uses: actions/upload-artifact@v2 + with: + name: parallel-lint-phar + path: ./parallel-lint.phar + + verify: + name: Validate binary on PHP ${{ matrix.php }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental == true }} + needs: + - bundle + + strategy: + matrix: + php: + - '5.4' + - '5.5' + - '5.6' + - '7.0' + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + + include: + - php: '8.1' + experimental: true + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - uses: actions/download-artifact@v2 + with: + name: parallel-lint-phar + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + - name: Run linter against codebase + run: php ./parallel-lint.phar src/ + + publish: + name: Add binary to release + runs-on: ubuntu-latest + needs: + - bundle + - verify + + steps: + - uses: actions/download-artifact@v2 + with: + name: parallel-lint-phar + + - name: Draft Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: true + prerelease: false + + - name: Upload Phar as Release Asset + id: upload-release-asset + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: parallel-lint.phar diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7d54101 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,139 @@ +name: Run unit and style tests + +on: + pull_request: + push: + branches: + - develop + - master + # Allow manually triggering the workflow. + workflow_dispatch: + +jobs: + lint: + name: Run style linter + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: cs2pr + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install Composer dependencies + uses: ramsey/composer-install@v1 + + - name: Run code sniffer + continue-on-error: true + run: vendor/bin/phpcs --report-full --report-checkstyle=./phpcs-report.xml + + - name: Show PHPCS results in PR + run: cs2pr ./phpcs-report.xml + + bundle: + name: Bundle binary + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 5.4 + extensions: exif, phar, openssl + coverage: none + ini-values: phar.readonly=Off + + - name: Install Box from GitHub + run: | + curl -LSs https://box-project.github.io/box2/installer.php | php + test -f ./box.phar + test -d ~/bin || mkdir ~/bin + mv ./box.phar ~/bin/box + ~/bin/box -V + echo "$HOME/bin" >> $GITHUB_PATH + + - name: Install Composer dependencies + uses: ramsey/composer-install@v1 + with: + composer-options: "--no-dev" + + - name: Building binary... + run: box build -v + + - uses: actions/upload-artifact@v2 + with: + name: parallel-lint-phar + path: ./parallel-lint.phar + + test: + name: Run tests on PHP ${{ matrix.php }} + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental == true }} + needs: + - bundle + + strategy: + matrix: + php: + - '5.3' + - '5.4' + - '5.5' + - '5.6' + - '7.0' + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + + include: + - php: '8.1' + experimental: true + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + + # Remove PHPCS as it has a minimum PHP requirements of PHP 5.4 and would block install on PHP 5.3. + - name: 'Composer: remove PHPCS' + if: ${{ matrix.php < 5.4 }} + run: composer remove --dev squizlabs/php_codesniffer --no-update + + - name: Install Composer dependencies + uses: ramsey/composer-install@v1 + + - name: 'Integration test 1 - linting own code, no colors' + continue-on-error: true + run: ./parallel-lint --exclude vendor --exclude tests/examples --no-colors . + + - name: 'Integration test 2 - linting own code' + run: ./parallel-lint --exclude vendor --exclude tests/examples . + + - name: 'Run unit tests PHP <= 5.5' + if: ${{ matrix.php < 5.6 }} + run: composer testphp5 + + - name: 'Run unit tests PHP >= 5.6' + if: ${{ matrix.php >= 5.6 }} + run: composer test + + - uses: actions/download-artifact@v2 + with: + name: parallel-lint-phar + + - name: Run linter against codebase using the phar + run: php ./parallel-lint.phar --exclude vendor --exclude tests/examples . diff --git a/.gitignore b/.gitignore index 8b7ef35..0822554 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /vendor composer.lock +.phpcs.xml +phpcs.xml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8b13987..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: php - -matrix: - include: - - php: 5.4 - dist: trusty - - php: 5.5 - dist: trusty - - php: 5.6 - - php: 7.0 - - php: 7.1 - - php: 7.2 - - php: 7.3 - - php: 7.4 - - php: "nightly" - - fast_finish: true - - allow_failures: - # Allow failures for unstable builds. - - php: "nightly" - -install: - - composer install --no-interaction --prefer-source - -script: - - ./vendor/bin/tester -p php tests - - ./parallel-lint --exclude vendor --exclude tests/examples --no-colors . - - ./parallel-lint --exclude vendor --exclude tests/examples . - - ./vendor/bin/phpcs --standard=phpcs-ruleset.xml -s src diff --git a/CHANGELOG.md b/CHANGELOG.md index e544f1b..16c15e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +[Unreleased]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.0...HEAD + +## [1.3.0] - 2021-04-07 + +### Added + +- Allow for multi-part file extensions to be passed using -e (like `-e php,php.dist`) from [@jrfnl](https://github.com/jrfnl). +- Added syntax error callback [#30](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/30) from [@arxeiss](https://github.com/arxeiss). +- Ignore PHP startup errors [#34](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/34) from [@jrfnl](https://github.com/jrfnl). +- Restore php 5.3 support [#51](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/51) from [@glensc](https://github.com/glensc). + +### Fixed + +- Determine skip lint process failure by status code instead of stderr content [#48](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/48) from [@jankonas](https://github.com/jankonas). + +### Changed + +- Improve wording in the readme [#52](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/52) from [@glensc](https://github.com/glensc). + +### Internal + +- Normalized composer.json from [@OndraM](https://github.com/OndraM). +- Updated PHPCS dependency from [@jrfnl](https://github.com/jrfnl). +- Cleaned coding style from [@jrfnl](https://github.com/jrfnl). +- Provide one true way to run the test suite [#37](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/37) from [@mfn](https://github.com/mfn). +- Travis: add build against PHP 8.0 and fix failing test [#41](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/41) from [@jrfnl](https://github.com/jrfnl). +- GitHub Actions for testing, and automatic phar creation [#46](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/46) from [@roelofr](https://github.com/roelofr). +- Add .github folder to .gitattributes export-ignore [#54](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/54) from [@glensc](https://github.com/glensc). +- Suggest to curl composer install via HTTPS [#53](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/53) from [@reedy](https://github.com/reedy). +- GH Actions: allow for manually triggering a workflow [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl). +- GH Actions: fix phar creation [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl). +- GH Actions: run the tests against all supported PHP versions [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl). +- GH Actions: report CS violations in the PR [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl). + +[1.3.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.2.0...v1.3.0 + ## [1.2.0] - 2020-04-04 ### Added @@ -26,3 +62,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added info about installing like not a dependency. - Cleaned readme - new organization from previous package. - Added checklist for new version from [@szepeviktor](https://github.com/szepeviktor). + +[1.2.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.1.0...v1.2.0 diff --git a/README.md b/README.md index 446428d..e5c762f 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,39 @@ # PHP Parallel Lint [![Downloads this Month](https://img.shields.io/packagist/dm/php-parallel-lint/php-parallel-lint.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint) -[![Build Status](https://travis-ci.org/php-parallel-lint/PHP-Parallel-Lint.svg?branch=master)](https://travis-ci.org/php-parallel-lint/PHP-Parallel-Lint) +[![Build Status](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml/badge.svg)](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml) [![License](https://poser.pugx.org/php-parallel-lint/php-parallel-lint/license.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint) -This tool checks syntax of PHP files faster than serial check with a fancier output. +This application checks syntax of PHP files in parallel. +It can output in plain text, colored text, json and checksyntax formats. +Additionally `blame` can be used to show commits that introduced the breakage. + Running parallel jobs in PHP is inspired by Nette framework tests. -This works from PHP 5.4 to 7.4 +The application is officially supported for use with PHP 5.3 to 8.0. ## Table of contents 1. [Installation](#installation) 2. [Example output](#example-output) -3. [Fork](#fork) -4. [Options for run](#options-for-run) -5. [Options for Symfony](#recommended-setting-for-usage-with-symfony-framework) +3. [History](#history) +4. [Command line options](#command-line-options) +5. [Recommended excludes for Symfony framework](#recommended-excludes-for-symfony-framework) 6. [Create Phar package](#create-phar-package) -7. [How upgrade](#how-upgrade) +7. [How to upgrade](#how-to-upgrade) ## Installation -Just run the following command to install it: +Install with `composer` as development dependency: composer require --dev php-parallel-lint/php-parallel-lint -When you cannot use tool as dependency then you can install as project. Command for it: +Alternatively you can install as a standalone `composer` project: composer create-project php-parallel-lint/php-parallel-lint /path/to/folder/php-parallel-lint /path/to/folder/php-parallel-lint/parallel-lint # running tool -For colored output also install the suggested package `php-parallel-lint/php-console-highlighter`: +For colored output, install the suggested package `php-parallel-lint/php-console-highlighter`: composer require --dev php-parallel-lint/php-console-highlighter @@ -39,37 +42,51 @@ For colored output also install the suggested package `php-parallel-lint/php-con ![Example use of tool with error](/tests/examples/example-images/use-error.png?raw=true "Example use of tool with error") -## Fork -This is a fork of [original project](https://github.com/JakubOnderka/PHP-Parallel-Lint). Why I forked it and why I am the right man? +## History + +This project was originally created by [@JakubOnderka] and released as +[jakub-onderka/php-parallel-lint]. + +Since then, Jakub has moved on to other interests and as of January 2020, the +second most active maintainer [@grogy] has taken over maintenance of the project +and given the project - and related dependencies - a new home in the PHP +Parallel Lint organisation. + +It is strongly recommended for existing users of the (unmaintained) +[jakub-onderka/php-parallel-lint] package to switch their dependency to +[php-parallel-lint/php-parallel-lint], see [How to upgrade](#how-to-upgrade) below. -- Project is used in many and projects. -- I am [second most active](https://github.com/JakubOnderka/PHP-Parallel-Lint/graphs/contributors) contributor in original project. -- Author does [not responds to issues and PRs](https://github.com/JakubOnderka/PHP-Parallel-Lint/pulls) and my mail messages. +[php-parallel-lint/php-parallel-lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint +[grogy/php-parallel-lint]: https://github.com/grogy/PHP-Parallel-Lint +[jakub-onderka/php-parallel-lint]: https://github.com/JakubOnderka/PHP-Parallel-Lint +[@JakubOnderka]: https://github.com/JakubOnderka +[@grogy]: https://github.com/grogy -## Options for run +## Command line options -- `-p ` Specify PHP-CGI executable to run (default: 'php'). -- `-s, --short` Set short_open_tag to On (default: Off). -- `-a, --asp` Set asp_tags to On (default: Off). -- `-e ` Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml,phpt) -- `--exclude` Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters. -- `-j ` Run jobs in parallel (default: 10). -- `--colors` Force enable colors in console output. -- `--no-colors` Disable colors in console output. -- `--no-progress` Disable progress in console output. -- `--checkstyle` Output results as Checkstyle XML. -- `--json` Output results as JSON string (require PHP 5.4). -- `--blame` Try to show git blame for row with error. -- `--git ` Path to Git executable to show blame message (default: 'git'). -- `--stdin` Load files and folder to test from standard input. -- `--ignore-fails` Ignore failed tests. -- `-h, --help` Print this help. -- `-V, --version` Display this application version. +- `-p ` Specify PHP-CGI executable to run (default: 'php'). +- `-s, --short` Set short_open_tag to On (default: Off). +- `-a, --asp` Set asp_tags to On (default: Off). +- `-e ` Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml,phpt) +- `--exclude` Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters. +- `-j ` Run jobs in parallel (default: 10). +- `--colors` Force enable colors in console output. +- `--no-colors` Disable colors in console output. +- `--no-progress` Disable progress in console output. +- `--checkstyle` Output results as Checkstyle XML. +- `--json` Output results as JSON string (requires PHP 5.4). +- `--blame` Try to show git blame for row with error. +- `--git ` Path to Git executable to show blame message (default: 'git'). +- `--stdin` Load files and folder to test from standard input. +- `--ignore-fails` Ignore failed tests. +- `--syntax-error-callback` File with syntax error callback for ability to modify error, see more in [example](doc/syntax-error-callback.md) +- `-h, --help` Print this help. +- `-V, --version` Display this application version. -## Recommended setting for usage with Symfony framework +## Recommended excludes for Symfony framework -For run from command line: +To run from the command line: vendor/bin/parallel-lint --exclude app --exclude vendor . @@ -81,14 +98,14 @@ PHP Parallel Lint supports [Box app](https://box-project.github.io/box2/) for cr curl -LSs https://box-project.github.io/box2/installer.php | php -and then run this command in parallel lint folder, which creates `parallel-lint.phar` file. +then run the build command in parallel lint folder, which creates `parallel-lint.phar` file. box build -## How upgrade +## How to upgrade -Are you using original package? You can easy use this fork. Steps for upgrade are: +Are you using `jakub-onderka/php-parallel-lint` package? You can switch to `php-parallel-lint/php-parallel-lint` using: composer remove --dev jakub-onderka/php-parallel-lint composer require --dev php-parallel-lint/php-parallel-lint diff --git a/appveyor.yml b/appveyor.yml index 08bdae4..15ae250 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,6 @@ install: - php composer.phar install --prefer-source --no-interaction test_script: - - vendor\bin\tester tests -p php + - php composer.phar test - php parallel-lint --exclude vendor --exclude tests\examples --no-colors . - php parallel-lint --exclude vendor --exclude tests\examples . diff --git a/box.json b/box.json index 951802d..8c6fc81 100644 --- a/box.json +++ b/box.json @@ -5,7 +5,7 @@ "Herrera\\Box\\Compactor\\Php" ], "extract": false, - "main": "parallel-lint.php", + "main": "parallel-lint", "files": [ "LICENSE" ], diff --git a/composer.json b/composer.json index 3c90b69..ce7ed04 100644 --- a/composer.json +++ b/composer.json @@ -3,32 +3,44 @@ "description": "This tool check syntax of PHP files about 20x faster than serial check.", "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint", "license": "BSD-2-Clause", + "authors": [ + { + "name": "Jakub Onderka", + "email": "ahoj@jakubonderka.cz" + } + ], "require": { - "php": ">=5.4.0", + "php": ">=5.3.0", "ext-json": "*" }, + "replace": { + "grogy/php-parallel-lint": "*", + "jakub-onderka/php-parallel-lint": "*" + }, "require-dev": { "nette/tester": "^1.3 || ^2.0", "php-parallel-lint/php-console-highlighter": "~0.3", - "squizlabs/php_codesniffer": "~3.0" + "squizlabs/php_codesniffer": "^3.5" }, "suggest": { "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet" }, - "authors": [ - { - "name": "Jakub Onderka", - "email": "ahoj@jakubonderka.cz" - } - ], + "config": { + "sort-packages": true + }, "autoload": { "classmap": [ "./" ] }, - "bin": ["parallel-lint"], - "replace": { - "jakub-onderka/php-parallel-lint": "*", - "grogy/php-parallel-lint": "*" + "bin": [ + "parallel-lint" + ], + "scripts": { + "test": "@php vendor/bin/tester -C -p php tests", + "testphp5": "@php vendor/bin/tester -p php tests" + }, + "scripts-descriptions": { + "test": "Run all tests!" } } diff --git a/doc/syntax-error-callback.md b/doc/syntax-error-callback.md new file mode 100644 index 0000000..955f5ef --- /dev/null +++ b/doc/syntax-error-callback.md @@ -0,0 +1,31 @@ +# Syntax Error Callback + +1. Set a path to a file with custom error callback to `--syntax-error-callback` option. +1. Create a class implementing `JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback` interface. File with the class must have the same name as the class inside. +1. Modify error before it is printed to the output. + +## Example configuration + +File `MyCustomErrorHandler.php` will be passed as an argument like `./parallel-lint --syntax-error-callback ./path/to/MyCustomErrorHandler.php .`.
+The content should look like: + +```php + +use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback; +use JakubOnderka\PhpParallelLint\SyntaxError; + +class MyCustomErrorHandler implements SyntaxErrorCallback { + /** + * @param SyntaxError $error + * @return SyntaxError + */ + public function errorFound(SyntaxError $error){ + // Return new SyntaxError with custom modification to FilePath or Message + // Or return custom implementation of SyntaxError extending the original one... + return new SyntaxError( + $error->getFilePath(), + $error->getMessage() + ); + } +} +``` diff --git a/parallel-lint b/parallel-lint index 0d77fdb..f44e110 100755 --- a/parallel-lint +++ b/parallel-lint @@ -30,19 +30,19 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ -if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50400) { - fwrite(STDERR, "PHP Parallel Lint require PHP 5.4.0 or newer." . PHP_EOL); +if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) { + fwrite(STDERR, "PHP Parallel Lint requires PHP 5.3.0 or newer." . PHP_EOL); exit(254); } -$autoloadLocations = [ +$autoloadLocations = array( getcwd() . '/vendor/autoload.php', getcwd() . '/../../autoload.php', __DIR__ . '/vendor/autoload.php', __DIR__ . '/../vendor/autoload.php', __DIR__ . '/../../../autoload.php', __DIR__ . '/../../autoload.php', -]; +); $loaded = false; foreach ($autoloadLocations as $autoload) { @@ -55,11 +55,13 @@ foreach ($autoloadLocations as $autoload) { if (!$loaded) { fwrite(STDERR, 'You must set up the project dependencies, run the following commands:' . PHP_EOL . - 'curl -s http://getcomposer.org/installer | php' . PHP_EOL . + 'curl -s https://getcomposer.org/installer | php' . PHP_EOL . 'php composer.phar install' . PHP_EOL ); exit(254); } +require_once __DIR__ . '/src/polyfill.php'; + $app = new JakubOnderka\PhpParallelLint\Application(); exit($app->run()); diff --git a/phpcs-ruleset.xml b/phpcs.xml.dist similarity index 66% rename from phpcs-ruleset.xml rename to phpcs.xml.dist index 8d730a6..e84650a 100644 --- a/phpcs-ruleset.xml +++ b/phpcs.xml.dist @@ -1,7 +1,38 @@ - + PHP Parallel Lint coding standard. + + + . + + + */tests/examples/* + */vendor/* + + + + + + + + + + + + + + + @@ -20,7 +51,9 @@ - + + /bin/skip-linting\.php$ + diff --git a/src/Application.php b/src/Application.php index 4517083..d38e42d 100644 --- a/src/Application.php +++ b/src/Application.php @@ -4,7 +4,7 @@ class Application { - const VERSION = '1.2.0'; + const VERSION = '1.3.0'; // Return codes const SUCCESS = 0, @@ -75,25 +75,26 @@ private function showOptions() { echo << Specify PHP-CGI executable to run (default: 'php'). - -s, --short Set short_open_tag to On (default: Off). - -a, -asp Set asp_tags to On (default: Off). - -e Check only files with selected extensions separated by comma. - (default: php,php3,php4,php5,phtml,phpt) - --exclude Exclude a file or directory. If you want exclude multiple items, - use multiple exclude parameters. - -j Run jobs in parallel (default: 10). - --colors Enable colors in console output. (disables auto detection of color support) - --no-colors Disable colors in console output. - --no-progress Disable progress in console output. - --json Output results as JSON string. - --checkstyle Output results as Checkstyle XML. - --blame Try to show git blame for row with error. - --git Path to Git executable to show blame message (default: 'git'). - --stdin Load files and folder to test from standard input. - --ignore-fails Ignore failed tests. - -h, --help Print this help. - -V, --version Display this application version + -p Specify PHP-CGI executable to run (default: 'php'). + -s, --short Set short_open_tag to On (default: Off). + -a, -asp Set asp_tags to On (default: Off). + -e Check only files with selected extensions separated by comma. + (default: php,php3,php4,php5,phtml,phpt) + --exclude Exclude a file or directory. If you want exclude multiple items, + use multiple exclude parameters. + -j Run jobs in parallel (default: 10). + --colors Enable colors in console output. (disables auto detection of color support) + --no-colors Disable colors in console output. + --no-progress Disable progress in console output. + --json Output results as JSON string. + --checkstyle Output results as Checkstyle XML. + --blame Try to show git blame for row with error. + --git Path to Git executable to show blame message (default: 'git'). + --stdin Load files and folder to test from standard input. + --ignore-fails Ignore failed tests. + --syntax-error-callback File with syntax error callback for ability to modify error + -h, --help Print this help. + -V, --version Display this application version HELP; } diff --git a/src/Contracts/SyntaxErrorCallback.php b/src/Contracts/SyntaxErrorCallback.php new file mode 100644 index 0000000..0db055f --- /dev/null +++ b/src/Contracts/SyntaxErrorCallback.php @@ -0,0 +1,13 @@ +setAspTagsEnabled($settings->aspTags); $parallelLint->setShortTagEnabled($settings->shortTag); $parallelLint->setShowDeprecated($settings->showDeprecated); + $parallelLint->setSyntaxErrorCallback($this->createSyntaxErrorCallback($settings)); $parallelLint->setProcessCallback(function ($status, $file) use ($output) { if ($status === ParallelLint::STATUS_OK) { @@ -134,7 +136,8 @@ protected function gitBlame(Result $result, Settings $settings) */ protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = array()) { - $extensions = array_flip($extensions); + $extensions = array_map('preg_quote', $extensions, array_fill(0, count($extensions), '`')); + $regex = '`\.(?:' . implode('|', $extensions) . ')$`iD'; $files = array(); foreach ($paths as $path) { @@ -151,11 +154,11 @@ protected function getFilesFromPaths(array $paths, array $extensions, array $exc \RecursiveIteratorIterator::CATCH_GET_CHILD ); + $iterator = new \RegexIterator($iterator, $regex); + /** @var \SplFileInfo[] $iterator */ foreach ($iterator as $directoryFile) { - if (isset($extensions[pathinfo($directoryFile->getFilename(), PATHINFO_EXTENSION)])) { - $files[] = (string) $directoryFile; - } + $files[] = (string) $directoryFile; } } else { throw new NotExistsPathException($path); @@ -166,6 +169,33 @@ protected function getFilesFromPaths(array $paths, array $extensions, array $exc return $files; } + + protected function createSyntaxErrorCallback(Settings $settings) + { + if ($settings->syntaxErrorCallbackFile === null) { + return null; + } + + $fullFilePath = realpath($settings->syntaxErrorCallbackFile); + if ($fullFilePath === false) { + throw new NotExistsPathException($settings->syntaxErrorCallbackFile); + } + + require_once $fullFilePath; + + $expectedClassName = basename($fullFilePath, '.php'); + if (!class_exists($expectedClassName)) { + throw new NotExistsClassException($expectedClassName, $settings->syntaxErrorCallbackFile); + } + + $callbackInstance = new $expectedClassName; + + if (!($callbackInstance instanceof SyntaxErrorCallback)) { + throw new NotImplementCallbackException($expectedClassName); + } + + return $callbackInstance; + } } class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator diff --git a/src/ParallelLint.php b/src/ParallelLint.php index 8c12a73..b1825f3 100644 --- a/src/ParallelLint.php +++ b/src/ParallelLint.php @@ -1,6 +1,7 @@ phpExecutable = $phpExecutable; @@ -92,7 +96,7 @@ public function lint(array $files) } else if ($process->containsError()) { $checkedFiles[] = $file; - $errors[] = new SyntaxError($file, $process->getSyntaxError()); + $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError())); $processCallback(self::STATUS_ERROR, $file); } else if ($process->isSuccess()) { @@ -111,7 +115,7 @@ public function lint(array $files) if (!empty($waiting)) { $skipLintProcess->waitForFinish(); - if ($skipLintProcess->getErrorOutput()) { + if ($skipLintProcess->isFail()) { $message = "Error in skip-linting.php process\nError output: {$skipLintProcess->getErrorOutput()}"; throw new \Exception($message); } @@ -131,7 +135,7 @@ public function lint(array $files) } else if ($process->containsError()) { $checkedFiles[] = $file; - $errors[] = new SyntaxError($file, $process->getSyntaxError()); + $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError())); $processCallback(self::STATUS_ERROR, $file); } else { @@ -259,4 +263,24 @@ public function setShowDeprecated($showDeprecated) return $this; } + + public function triggerSyntaxErrorCallback($syntaxError) + { + if ($this->syntaxErrorCallback === null) { + return $syntaxError; + } + + return $this->syntaxErrorCallback->errorFound($syntaxError); + } + + /** + * @param SyntaxErrorCallback|null $syntaxErrorCallback + * @return ParallelLint + */ + public function setSyntaxErrorCallback($syntaxErrorCallback) + { + $this->syntaxErrorCallback = $syntaxErrorCallback; + + return $this; + } } diff --git a/src/Process/PhpProcess.php b/src/Process/PhpProcess.php index de9eee1..0df293a 100644 --- a/src/Process/PhpProcess.php +++ b/src/Process/PhpProcess.php @@ -23,8 +23,11 @@ public function __construct(PhpExecutable $phpExecutable, array $parameters = ar */ private function constructParameters(array $parameters, $isHhvm) { + // Always ignore PHP startup errors ("Unable to load library...") in sub-processes. + array_unshift($parameters, '-d display_startup_errors=0'); + if ($isHhvm) { - $parameters = array_merge(array('-php'), $parameters); + array_unshift($parameters, '-php'); } return $parameters; diff --git a/src/Settings.php b/src/Settings.php index 24071af..3e03a71 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -103,6 +103,12 @@ class Settings */ public $showDeprecated = false; + /** + * Path to a file with syntax error callback + * @var string|null + */ + public $syntaxErrorCallbackFile = null; + /** * @param array $paths */ @@ -197,6 +203,10 @@ public static function parseArguments(array $arguments) $settings->showDeprecated = true; break; + case '--syntax-error-callback': + $settings->syntaxErrorCallbackFile = $arguments->getNext(); + break; + default: throw new InvalidArgumentException($argument); } diff --git a/src/exceptions.php b/src/exceptions.php index fd47ddc..3893b83 100644 --- a/src/exceptions.php +++ b/src/exceptions.php @@ -49,3 +49,42 @@ public function getPath() return $this->path; } } + +class NotExistsClassException extends Exception +{ + protected $className; + protected $fileName; + + public function __construct($className, $fileName) + { + $this->className = $className; + $this->fileName = $fileName; + $this->message = "Class with name '$className' does not exists in file '$fileName'"; + } + + public function getClassName() + { + return $this->className; + } + + public function getFileName() + { + return $this->fileName; + } +} + +class NotImplementCallbackException extends Exception +{ + protected $className; + + public function __construct($className) + { + $this->className = $className; + $this->message = "Class '$className' does not implement SyntaxErrorCallback interface."; + } + + public function getClassName() + { + return $this->className; + } +} diff --git a/src/polyfill.php b/src/polyfill.php new file mode 100644 index 0000000..79b3049 --- /dev/null +++ b/src/polyfill.php @@ -0,0 +1,15 @@ +prepareSettings(); $settings->paths = array('path/for-not-found/'); $manager = $this->getManager($settings); - Assert::exception(function() use ($manager, $settings) { + Assert::exception(function () use ($manager, $settings) { $manager->run($settings); }, 'JakubOnderka\PhpParallelLint\NotExistsPathException'); } @@ -29,7 +29,7 @@ class ManagerRunTest extends Tester\TestCase $settings = $this->prepareSettings(); $settings->paths = array('examples/example-01/'); $manager = $this->getManager($settings); - Assert::exception(function() use ($manager, $settings) { + Assert::exception(function () use ($manager, $settings) { $manager->run($settings); }, 'JakubOnderka\PhpParallelLint\Exception', 'No file found to check.'); } @@ -88,6 +88,23 @@ class ManagerRunTest extends Tester\TestCase Assert::false($result->hasError()); } + /** + * Note: the `example.php-dist` file contains a parse error. + * With multi-part extensions being escaped before being used in the RegexIterator, + * this file will not be included in the scan and the test will pass. + */ + public function testMultiPartExtensions() + { + $settings = $this->prepareSettings(); + $settings->paths = array('examples/example-06/'); + + $settings->extensions = array('php', 'php.dist'); + + $manager = $this->getManager($settings); + $result = $manager->run($settings); + Assert::false($result->hasError()); + } + /** * @param Settings $settings * @return Manager diff --git a/tests/ParallelLint.lint.phpt b/tests/ParallelLint.lint.phpt index b42a044..4ed584c 100644 --- a/tests/ParallelLint.lint.phpt +++ b/tests/ParallelLint.lint.phpt @@ -101,8 +101,8 @@ class ParallelLintLintTest extends Tester\TestCase Assert::false($result->hasSyntaxError()); Assert::equal(0, count($result->getErrors())); - if (PHP_VERSION_ID < 70000) { - Tester\Environment::skip('test for php version > 7.0'); + if (PHP_VERSION_ID < 70000 || PHP_VERSION_ID >= 80000 ) { + Tester\Environment::skip('test for php version 7.0-7.4'); } $parallelLint = new ParallelLint($this->getPhpExecutable()); diff --git a/tests/Settings.parseArguments.phpt b/tests/Settings.parseArguments.phpt index 60aa7c6..0a83673 100644 --- a/tests/Settings.parseArguments.phpt +++ b/tests/Settings.parseArguments.phpt @@ -27,6 +27,7 @@ class SettingsParseArgumentsTest extends Tester\TestCase $expectedSettings->colors = Settings::AUTODETECT; $expectedSettings->showProgress = true; $expectedSettings->format = Settings::FORMAT_TEXT; + $expectedSettings->syntaxErrorCallbackFile = null; Assert::equal($expectedSettings->shortTag, $settings->shortTag); Assert::equal($expectedSettings->aspTags, $settings->aspTags); @@ -37,6 +38,7 @@ class SettingsParseArgumentsTest extends Tester\TestCase Assert::equal($expectedSettings->colors, $settings->colors); Assert::equal($expectedSettings->showProgress, $settings->showProgress); Assert::equal($expectedSettings->format, $settings->format); + Assert::equal($expectedSettings->syntaxErrorCallbackFile, $settings->syntaxErrorCallbackFile); } public function testMoreArguments() @@ -108,6 +110,30 @@ class SettingsParseArgumentsTest extends Tester\TestCase $settings = Settings::parseArguments($argv); Assert::equal(Settings::FORMAT_CHECKSTYLE, $settings->format); } + + public function testExtensions() + { + $commandLine = './parallel-lint -e php,php.dist,phpt .'; + $argv = explode(" ", $commandLine); + $settings = Settings::parseArguments($argv); + + $expectedSettings = new Settings(); + $expectedSettings->extensions = array('php', 'php.dist', 'phpt'); + + Assert::equal($expectedSettings->extensions, $settings->extensions); + } + + public function testFailCallaback() + { + $commandLine = "./parallel-lint --syntax-error-callback ./path/to/my_custom_callback_file.php ."; + $argv = explode(" ", $commandLine); + $settings = Settings::parseArguments($argv); + + $expectedSettings = new Settings(); + $expectedSettings->syntaxErrorCallbackFile = "./path/to/my_custom_callback_file.php"; + + Assert::equal($expectedSettings->syntaxErrorCallbackFile, $settings->syntaxErrorCallbackFile); + } } $testCase = new SettingsParseArgumentsTest; diff --git a/tests/examples/example-06/example.php b/tests/examples/example-06/example.php new file mode 100644 index 0000000..2186cb4 --- /dev/null +++ b/tests/examples/example-06/example.php @@ -0,0 +1,4 @@ +