mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-13 00:07:26 +00:00
contrib: add parallel-lint 1.3.1
Avoid pulling in composer. Looks easy enough to manually load classes.
This commit is contained in:
parent
cff444c9df
commit
062d51889e
111
contrib/parallel-lint/CHANGELOG.md
Normal file
111
contrib/parallel-lint/CHANGELOG.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
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.1] - 2021-08-13
|
||||
|
||||
### Added
|
||||
|
||||
- Extend by the Code Climate output format [#50] from [@lukas9393].
|
||||
|
||||
### Fixed
|
||||
|
||||
- PHP 8.1: silence the deprecation notices about missing return types [#64] from [@jrfnl].
|
||||
|
||||
### Internal
|
||||
|
||||
- Reformat changelog to use reflinks in changelog entries [#58] from [@glensc].
|
||||
|
||||
[1.3.1]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.0...1.3.1
|
||||
|
||||
[#50]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/50
|
||||
[#58]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/58
|
||||
[#64]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/64
|
||||
|
||||
## [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].
|
||||
- Added syntax error callback [#30] from [@arxeiss].
|
||||
- Ignore PHP startup errors [#34] from [@jrfnl].
|
||||
- Restore php 5.3 support [#51] from [@glensc].
|
||||
|
||||
### Fixed
|
||||
|
||||
- Determine skip lint process failure by status code instead of stderr content [#48] from [@jankonas].
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve wording in the readme [#52] from [@glensc].
|
||||
|
||||
### Internal
|
||||
|
||||
- Normalized composer.json from [@OndraM].
|
||||
- Updated PHPCS dependency from [@jrfnl].
|
||||
- Cleaned coding style from [@jrfnl].
|
||||
- Provide one true way to run the test suite [#37] from [@mfn].
|
||||
- Travis: add build against PHP 8.0 and fix failing test [#41] from [@jrfnl].
|
||||
- GitHub Actions for testing, and automatic phar creation [#46] from [@roelofr].
|
||||
- Add .github folder to .gitattributes export-ignore [#54] from [@glensc].
|
||||
- Suggest to curl composer install via HTTPS [#53] from [@reedy].
|
||||
- GH Actions: allow for manually triggering a workflow [#55] from [@jrfnl].
|
||||
- GH Actions: fix phar creation [#55] from [@jrfnl].
|
||||
- GH Actions: run the tests against all supported PHP versions [#55] from [@jrfnl].
|
||||
- GH Actions: report CS violations in the PR [#55] from [@jrfnl].
|
||||
|
||||
[1.3.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.2.0...v1.3.0
|
||||
[#30]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/30
|
||||
[#34]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/34
|
||||
[#37]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/37
|
||||
[#41]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/41
|
||||
[#46]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/46
|
||||
[#48]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/48
|
||||
[#51]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/51
|
||||
[#52]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/52
|
||||
[#53]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/53
|
||||
[#54]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/54
|
||||
[#55]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55
|
||||
|
||||
## [1.2.0] - 2020-04-04
|
||||
|
||||
### Added
|
||||
|
||||
- Added changelog.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed vendor location for running from other folder from [@Erkens].
|
||||
|
||||
### Internal
|
||||
|
||||
- Added a .gitattributes file from [@jrfnl], thanks for issue to [@ondrejmirtes].
|
||||
- Fixed incorrect unit tests from [@jrfnl].
|
||||
- Fixed minor grammatical errors from [@jrfnl].
|
||||
- Added Travis: test against nightly (= PHP 8) from [@jrfnl].
|
||||
- Travis: removed sudo from [@jrfnl].
|
||||
- Added info about installing like not a dependency.
|
||||
- Cleaned readme - new organization from previous package.
|
||||
- Added checklist for new version from [@szepeviktor].
|
||||
|
||||
[1.2.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.1.0...v1.2.0
|
||||
|
||||
[@Erkens]: https://github.com/Erkens
|
||||
[@OndraM]: https://github.com/OndraM
|
||||
[@arxeiss]: https://github.com/arxeiss
|
||||
[@glensc]: https://github.com/glensc
|
||||
[@jankonas]: https://github.com/jankonas
|
||||
[@jrfnl]: https://github.com/jrfnl
|
||||
[@mfn]: https://github.com/mfn
|
||||
[@ondrejmirtes]: https://github.com/ondrejmirtes
|
||||
[@reedy]: https://github.com/reedy
|
||||
[@roelofr]: https://github.com/roelofr
|
||||
[@szepeviktor]: https://github.com/szepeviktor
|
||||
[@lukas9393]: https://github.com/lukas9393
|
||||
|
||||
26
contrib/parallel-lint/LICENSE
Normal file
26
contrib/parallel-lint/LICENSE
Normal file
@ -0,0 +1,26 @@
|
||||
Copyright (c) 2012, Jakub Onderka
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
112
contrib/parallel-lint/README.md
Normal file
112
contrib/parallel-lint/README.md
Normal file
@ -0,0 +1,112 @@
|
||||
# PHP Parallel Lint
|
||||
|
||||
[](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
|
||||
[](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml)
|
||||
[](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
|
||||
|
||||
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.
|
||||
|
||||
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. [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 to upgrade](#how-to-upgrade)
|
||||
|
||||
## Installation
|
||||
|
||||
Install with `composer` as development dependency:
|
||||
|
||||
composer require --dev php-parallel-lint/php-parallel-lint
|
||||
|
||||
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, install the suggested package `php-parallel-lint/php-console-highlighter`:
|
||||
|
||||
composer require --dev php-parallel-lint/php-console-highlighter
|
||||
|
||||
## Example output
|
||||
|
||||

|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
[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
|
||||
|
||||
## Command line options
|
||||
|
||||
- `-p <php>` 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 <ext>` 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 <num>` Run <num> 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).
|
||||
- `--gitlab` Output results for the GitLab Code Quality widget (requires PHP 5.4), see more in [Code Quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html) documentation.
|
||||
- `--blame` Try to show git blame for row with error.
|
||||
- `--git <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 excludes for Symfony framework
|
||||
|
||||
To run from the command line:
|
||||
|
||||
vendor/bin/parallel-lint --exclude app --exclude vendor .
|
||||
|
||||
## Create Phar package
|
||||
|
||||
PHP Parallel Lint supports [Box app](https://box-project.github.io/box2/) for creating Phar package. First, install box app:
|
||||
|
||||
|
||||
curl -LSs https://box-project.github.io/box2/installer.php | php
|
||||
|
||||
|
||||
then run the build command in parallel lint folder, which creates `parallel-lint.phar` file.
|
||||
|
||||
|
||||
box build
|
||||
|
||||
## How to upgrade
|
||||
|
||||
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
|
||||
19
contrib/parallel-lint/bin/skip-linting.php
Normal file
19
contrib/parallel-lint/bin/skip-linting.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
$stdin = fopen('php://stdin', 'r');
|
||||
$input = stream_get_contents($stdin);
|
||||
fclose($stdin);
|
||||
|
||||
foreach (explode(PHP_EOL, $input) as $file) {
|
||||
$skip = false;
|
||||
$f = @fopen($file, 'r');
|
||||
if ($f) {
|
||||
$firstLine = fgets($f);
|
||||
@fclose($f);
|
||||
|
||||
if (preg_match('~<?php\\s*\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m)) {
|
||||
$skip = version_compare(PHP_VERSION, $m[2], $m[1]) === false;
|
||||
}
|
||||
}
|
||||
|
||||
echo $file . ';' . ($skip ? '1' : '0') . PHP_EOL;
|
||||
}
|
||||
48
contrib/parallel-lint/parallel-lint
Executable file
48
contrib/parallel-lint/parallel-lint
Executable file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2014, Jakub Onderka
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
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 < 50300) {
|
||||
fwrite(STDERR, "PHP Parallel Lint requires PHP 5.3.0 or newer." . PHP_EOL);
|
||||
exit(254);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/src/Process/Process.php';
|
||||
require_once __DIR__ . '/src/Process/PhpProcess.php';
|
||||
foreach (glob(__DIR__ . '/src/*/*.php') as $file) {
|
||||
require_once $file;
|
||||
}
|
||||
foreach (glob(__DIR__ . '/src/*.php') as $file) {
|
||||
require_once $file;
|
||||
}
|
||||
|
||||
$app = new JakubOnderka\PhpParallelLint\Application();
|
||||
exit($app->run());
|
||||
125
contrib/parallel-lint/src/Application.php
Normal file
125
contrib/parallel-lint/src/Application.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
class Application
|
||||
{
|
||||
const VERSION = '1.3.1';
|
||||
|
||||
// Return codes
|
||||
const SUCCESS = 0,
|
||||
WITH_ERRORS = 1,
|
||||
FAILED = 254; // Error code 255 is reserved for PHP itself
|
||||
|
||||
/**
|
||||
* Run the application
|
||||
* @return int Return code
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if (in_array('proc_open', explode(',', ini_get('disable_functions')))) {
|
||||
echo "Function 'proc_open' is required, but it is disabled by the 'disable_functions' ini setting.", PHP_EOL;
|
||||
return self::FAILED;
|
||||
}
|
||||
|
||||
if (in_array('-h', $_SERVER['argv']) || in_array('--help', $_SERVER['argv'])) {
|
||||
$this->showUsage();
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if (in_array('-V', $_SERVER['argv']) || in_array('--version', $_SERVER['argv'])) {
|
||||
$this->showVersion();
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
try {
|
||||
$settings = Settings::parseArguments($_SERVER['argv']);
|
||||
if ($settings->stdin) {
|
||||
$settings->addPaths(Settings::getPathsFromStdIn());
|
||||
}
|
||||
if (empty($settings->paths)) {
|
||||
$this->showUsage();
|
||||
return self::FAILED;
|
||||
}
|
||||
$manager = new Manager;
|
||||
$result = $manager->run($settings);
|
||||
if ($settings->ignoreFails) {
|
||||
return $result->hasSyntaxError() ? self::WITH_ERRORS : self::SUCCESS;
|
||||
} else {
|
||||
return $result->hasError() ? self::WITH_ERRORS : self::SUCCESS;
|
||||
}
|
||||
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo "Invalid option {$e->getArgument()}", PHP_EOL, PHP_EOL;
|
||||
$this->showOptions();
|
||||
return self::FAILED;
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($settings) && $settings->format === Settings::FORMAT_JSON) {
|
||||
echo json_encode($e);
|
||||
} else {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
return self::FAILED;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
echo $e->getMessage(), PHP_EOL;
|
||||
return self::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the options
|
||||
*/
|
||||
private function showOptions()
|
||||
{
|
||||
echo <<<HELP
|
||||
Options:
|
||||
-p <php> 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 <ext> 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 <num> Run <num> 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.
|
||||
--gitlab Output results for the GitLab Code Quality Widget.
|
||||
--checkstyle Output results as Checkstyle XML.
|
||||
--blame Try to show git blame for row with error.
|
||||
--git <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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the current version
|
||||
*/
|
||||
private function showVersion()
|
||||
{
|
||||
echo 'PHP Parallel Lint version ' . self::VERSION.PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows usage
|
||||
*/
|
||||
private function showUsage()
|
||||
{
|
||||
$this->showVersion();
|
||||
echo <<<USAGE
|
||||
-------------------------------
|
||||
Usage:
|
||||
parallel-lint [sa] [-p php] [-e ext] [-j num] [--exclude dir] [files or directories]
|
||||
|
||||
USAGE;
|
||||
$this->showOptions();
|
||||
}
|
||||
}
|
||||
13
contrib/parallel-lint/src/Contracts/SyntaxErrorCallback.php
Normal file
13
contrib/parallel-lint/src/Contracts/SyntaxErrorCallback.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint\Contracts;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\SyntaxError;
|
||||
|
||||
interface SyntaxErrorCallback
|
||||
{
|
||||
/**
|
||||
* @param SyntaxError $error
|
||||
* @return SyntaxError
|
||||
*/
|
||||
public function errorFound(SyntaxError $error);
|
||||
}
|
||||
226
contrib/parallel-lint/src/Error.php
Normal file
226
contrib/parallel-lint/src/Error.php
Normal file
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class Error implements \JsonSerializable
|
||||
{
|
||||
/** @var string */
|
||||
protected $filePath;
|
||||
|
||||
/** @var string */
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param string $message
|
||||
*/
|
||||
public function __construct($filePath, $message)
|
||||
{
|
||||
$this->filePath = $filePath;
|
||||
$this->message = rtrim($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilePath()
|
||||
{
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getShortFilePath()
|
||||
{
|
||||
$cwd = getcwd();
|
||||
|
||||
if ($cwd === '/') {
|
||||
// For root directory in unix, do not modify path
|
||||
return $this->filePath;
|
||||
}
|
||||
|
||||
return preg_replace('/' . preg_quote($cwd, '/') . '/', '', $this->filePath, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.4.0)<br/>
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'type' => 'error',
|
||||
'file' => $this->getFilePath(),
|
||||
'message' => $this->getMessage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Blame implements \JsonSerializable
|
||||
{
|
||||
public $name;
|
||||
|
||||
public $email;
|
||||
|
||||
/** @var \DateTime */
|
||||
public $datetime;
|
||||
|
||||
public $commitHash;
|
||||
|
||||
public $summary;
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.4.0)<br/>
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'datetime' => $this->datetime,
|
||||
'commitHash' => $this->commitHash,
|
||||
'summary' => $this->summary,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class SyntaxError extends Error
|
||||
{
|
||||
/** @var Blame */
|
||||
private $blame;
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getLine()
|
||||
{
|
||||
preg_match('~on line ([0-9]+)$~', $this->message, $matches);
|
||||
|
||||
if ($matches && isset($matches[1])) {
|
||||
$onLine = (int) $matches[1];
|
||||
return $onLine;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $translateTokens
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function getNormalizedMessage($translateTokens = false)
|
||||
{
|
||||
$message = preg_replace('~^(Parse|Fatal) error: (syntax error, )?~', '', $this->message);
|
||||
$message = preg_replace('~ in ' . preg_quote(basename($this->filePath)) . ' on line [0-9]+$~', '', $message);
|
||||
$message = ucfirst($message);
|
||||
|
||||
if ($translateTokens) {
|
||||
$message = $this->translateTokens($message);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Blame $blame
|
||||
*/
|
||||
public function setBlame(Blame $blame)
|
||||
{
|
||||
$this->blame = $blame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Blame
|
||||
*/
|
||||
public function getBlame()
|
||||
{
|
||||
return $this->blame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @return string
|
||||
*/
|
||||
protected function translateTokens($message)
|
||||
{
|
||||
static $translateTokens = array(
|
||||
'T_FILE' => '__FILE__',
|
||||
'T_FUNC_C' => '__FUNCTION__',
|
||||
'T_HALT_COMPILER' => '__halt_compiler()',
|
||||
'T_INC' => '++',
|
||||
'T_IS_EQUAL' => '==',
|
||||
'T_IS_GREATER_OR_EQUAL' => '>=',
|
||||
'T_IS_IDENTICAL' => '===',
|
||||
'T_IS_NOT_IDENTICAL' => '!==',
|
||||
'T_IS_SMALLER_OR_EQUAL' => '<=',
|
||||
'T_LINE' => '__LINE__',
|
||||
'T_METHOD_C' => '__METHOD__',
|
||||
'T_MINUS_EQUAL' => '-=',
|
||||
'T_MOD_EQUAL' => '%=',
|
||||
'T_MUL_EQUAL' => '*=',
|
||||
'T_NS_C' => '__NAMESPACE__',
|
||||
'T_NS_SEPARATOR' => '\\',
|
||||
'T_OBJECT_OPERATOR' => '->',
|
||||
'T_OR_EQUAL' => '|=',
|
||||
'T_PAAMAYIM_NEKUDOTAYIM' => '::',
|
||||
'T_PLUS_EQUAL' => '+=',
|
||||
'T_SL' => '<<',
|
||||
'T_SL_EQUAL' => '<<=',
|
||||
'T_SR' => '>>',
|
||||
'T_SR_EQUAL' => '>>=',
|
||||
'T_START_HEREDOC' => '<<<',
|
||||
'T_XOR_EQUAL' => '^=',
|
||||
'T_ECHO' => 'echo'
|
||||
);
|
||||
|
||||
return preg_replace_callback('~T_([A-Z_]*)~', function ($matches) use ($translateTokens) {
|
||||
list($tokenName) = $matches;
|
||||
if (isset($translateTokens[$tokenName])) {
|
||||
$operator = $translateTokens[$tokenName];
|
||||
return "$operator ($tokenName)";
|
||||
}
|
||||
|
||||
return $tokenName;
|
||||
}, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.4.0)<br/>
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'type' => 'syntaxError',
|
||||
'file' => $this->getFilePath(),
|
||||
'line' => $this->getLine(),
|
||||
'message' => $this->getMessage(),
|
||||
'normalizeMessage' => $this->getNormalizedMessage(),
|
||||
'blame' => $this->blame,
|
||||
);
|
||||
}
|
||||
}
|
||||
127
contrib/parallel-lint/src/ErrorFormatter.php
Normal file
127
contrib/parallel-lint/src/ErrorFormatter.php
Normal file
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use JakubOnderka\PhpConsoleColor\ConsoleColor;
|
||||
use JakubOnderka\PhpConsoleHighlighter\Highlighter;
|
||||
|
||||
class ErrorFormatter
|
||||
{
|
||||
/** @var string */
|
||||
private $useColors;
|
||||
|
||||
/** @var bool */
|
||||
private $forceColors;
|
||||
|
||||
/** @var bool */
|
||||
private $translateTokens;
|
||||
|
||||
public function __construct($useColors = Settings::AUTODETECT, $translateTokens = false, $forceColors = false)
|
||||
{
|
||||
$this->useColors = $useColors;
|
||||
$this->forceColors = $forceColors;
|
||||
$this->translateTokens = $translateTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Error $error
|
||||
* @return string
|
||||
*/
|
||||
public function format(Error $error)
|
||||
{
|
||||
if ($error instanceof SyntaxError) {
|
||||
return $this->formatSyntaxErrorMessage($error);
|
||||
} else {
|
||||
if ($error->getMessage()) {
|
||||
return $error->getMessage();
|
||||
} else {
|
||||
return "Unknown error for file '{$error->getFilePath()}'.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SyntaxError $error
|
||||
* @param bool $withCodeSnipped
|
||||
* @return string
|
||||
*/
|
||||
public function formatSyntaxErrorMessage(SyntaxError $error, $withCodeSnipped = true)
|
||||
{
|
||||
$string = "Parse error: {$error->getShortFilePath()}";
|
||||
|
||||
if ($error->getLine()) {
|
||||
$onLine = $error->getLine();
|
||||
$string .= ":$onLine" . PHP_EOL;
|
||||
|
||||
if ($withCodeSnipped) {
|
||||
if ($this->useColors !== Settings::DISABLED) {
|
||||
$string .= $this->getColoredCodeSnippet($error->getFilePath(), $onLine);
|
||||
} else {
|
||||
$string .= $this->getCodeSnippet($error->getFilePath(), $onLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$string .= $error->getNormalizedMessage($this->translateTokens);
|
||||
|
||||
if ($error->getBlame()) {
|
||||
$blame = $error->getBlame();
|
||||
$shortCommitHash = substr($blame->commitHash, 0, 8);
|
||||
$dateTime = $blame->datetime->format('c');
|
||||
$string .= PHP_EOL . "Blame {$blame->name} <{$blame->email}>, commit '$shortCommitHash' from $dateTime";
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param int $lineNumber
|
||||
* @param int $linesBefore
|
||||
* @param int $linesAfter
|
||||
* @return string
|
||||
*/
|
||||
protected function getCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
|
||||
{
|
||||
$lines = file($filePath);
|
||||
|
||||
$offset = $lineNumber - $linesBefore - 1;
|
||||
$offset = max($offset, 0);
|
||||
$length = $linesAfter + $linesBefore + 1;
|
||||
$lines = array_slice($lines, $offset, $length, $preserveKeys = true);
|
||||
|
||||
end($lines);
|
||||
$lineStrlen = strlen(key($lines) + 1);
|
||||
|
||||
$snippet = '';
|
||||
foreach ($lines as $i => $line) {
|
||||
$snippet .= ($lineNumber === $i + 1 ? ' > ' : ' ');
|
||||
$snippet .= str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ' . rtrim($line) . PHP_EOL;
|
||||
}
|
||||
|
||||
return $snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param int $lineNumber
|
||||
* @param int $linesBefore
|
||||
* @param int $linesAfter
|
||||
* @return string
|
||||
*/
|
||||
protected function getColoredCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
|
||||
{
|
||||
if (
|
||||
!class_exists('\JakubOnderka\PhpConsoleHighlighter\Highlighter') ||
|
||||
!class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')
|
||||
) {
|
||||
return $this->getCodeSnippet($filePath, $lineNumber, $linesBefore, $linesAfter);
|
||||
}
|
||||
|
||||
$colors = new ConsoleColor();
|
||||
$colors->setForceStyle($this->forceColors);
|
||||
$highlighter = new Highlighter($colors);
|
||||
|
||||
$fileContent = file_get_contents($filePath);
|
||||
return $highlighter->getCodeSnippet($fileContent, $lineNumber, $linesBefore, $linesAfter);
|
||||
}
|
||||
}
|
||||
293
contrib/parallel-lint/src/Manager.php
Normal file
293
contrib/parallel-lint/src/Manager.php
Normal file
@ -0,0 +1,293 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
|
||||
use JakubOnderka\PhpParallelLint\Process\GitBlameProcess;
|
||||
use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class Manager
|
||||
{
|
||||
/** @var Output */
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @param null|Settings $settings
|
||||
* @return Result
|
||||
* @throws Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run(Settings $settings = null)
|
||||
{
|
||||
$settings = $settings ?: new Settings;
|
||||
$output = $this->output ?: $this->getDefaultOutput($settings);
|
||||
|
||||
$phpExecutable = PhpExecutable::getPhpExecutable($settings->phpExecutable);
|
||||
$olderThanPhp54 = $phpExecutable->getVersionId() < 50400; // From PHP version 5.4 are tokens translated by default
|
||||
$translateTokens = $phpExecutable->isIsHhvmType() || $olderThanPhp54;
|
||||
|
||||
$output->writeHeader($phpExecutable->getVersionId(), $settings->parallelJobs, $phpExecutable->getHhvmVersion());
|
||||
|
||||
$files = $this->getFilesFromPaths($settings->paths, $settings->extensions, $settings->excluded);
|
||||
|
||||
if (empty($files)) {
|
||||
throw new Exception('No file found to check.');
|
||||
}
|
||||
|
||||
$output->setTotalFileCount(count($files));
|
||||
|
||||
$parallelLint = new ParallelLint($phpExecutable, $settings->parallelJobs);
|
||||
$parallelLint->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) {
|
||||
$output->ok();
|
||||
} else if ($status === ParallelLint::STATUS_SKIP) {
|
||||
$output->skip();
|
||||
} else if ($status === ParallelLint::STATUS_ERROR) {
|
||||
$output->error();
|
||||
} else {
|
||||
$output->fail();
|
||||
}
|
||||
});
|
||||
|
||||
$result = $parallelLint->lint($files);
|
||||
|
||||
if ($settings->blame) {
|
||||
$this->gitBlame($result, $settings);
|
||||
}
|
||||
|
||||
$output->writeResult($result, new ErrorFormatter($settings->colors, $translateTokens), $settings->ignoreFails);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Output $output
|
||||
*/
|
||||
public function setOutput(Output $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Settings $settings
|
||||
* @return Output
|
||||
*/
|
||||
protected function getDefaultOutput(Settings $settings)
|
||||
{
|
||||
$writer = new ConsoleWriter;
|
||||
switch ($settings->format) {
|
||||
case Settings::FORMAT_JSON:
|
||||
return new JsonOutput($writer);
|
||||
case Settings::FORMAT_GITLAB:
|
||||
return new GitLabOutput($writer);
|
||||
case Settings::FORMAT_CHECKSTYLE:
|
||||
return new CheckstyleOutput($writer);
|
||||
}
|
||||
|
||||
if ($settings->colors === Settings::DISABLED) {
|
||||
$output = new TextOutput($writer);
|
||||
} else {
|
||||
$output = new TextOutputColored($writer, $settings->colors);
|
||||
}
|
||||
|
||||
$output->showProgress = $settings->showProgress;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Result $result
|
||||
* @param Settings $settings
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function gitBlame(Result $result, Settings $settings)
|
||||
{
|
||||
if (!GitBlameProcess::gitExists($settings->gitExecutable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($result->getErrors() as $error) {
|
||||
if ($error instanceof SyntaxError) {
|
||||
$process = new GitBlameProcess($settings->gitExecutable, $error->getFilePath(), $error->getLine());
|
||||
$process->waitForFinish();
|
||||
|
||||
if ($process->isSuccess()) {
|
||||
$blame = new Blame;
|
||||
$blame->name = $process->getAuthor();
|
||||
$blame->email = $process->getAuthorEmail();
|
||||
$blame->datetime = $process->getAuthorTime();
|
||||
$blame->commitHash = $process->getCommitHash();
|
||||
$blame->summary = $process->getSummary();
|
||||
|
||||
$error->setBlame($blame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $paths
|
||||
* @param array $extensions
|
||||
* @param array $excluded
|
||||
* @return array
|
||||
* @throws NotExistsPathException
|
||||
*/
|
||||
protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = array())
|
||||
{
|
||||
$extensions = array_map('preg_quote', $extensions, array_fill(0, count($extensions), '`'));
|
||||
$regex = '`\.(?:' . implode('|', $extensions) . ')$`iD';
|
||||
$files = array();
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (is_file($path)) {
|
||||
$files[] = $path;
|
||||
} else if (is_dir($path)) {
|
||||
$iterator = new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS);
|
||||
if (!empty($excluded)) {
|
||||
$iterator = new RecursiveDirectoryFilterIterator($iterator, $excluded);
|
||||
}
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
$iterator,
|
||||
\RecursiveIteratorIterator::LEAVES_ONLY,
|
||||
\RecursiveIteratorIterator::CATCH_GET_CHILD
|
||||
);
|
||||
|
||||
$iterator = new \RegexIterator($iterator, $regex);
|
||||
|
||||
/** @var \SplFileInfo[] $iterator */
|
||||
foreach ($iterator as $directoryFile) {
|
||||
$files[] = (string) $directoryFile;
|
||||
}
|
||||
} else {
|
||||
throw new NotExistsPathException($path);
|
||||
}
|
||||
}
|
||||
|
||||
$files = array_unique($files);
|
||||
|
||||
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
|
||||
{
|
||||
/** @var \RecursiveDirectoryIterator */
|
||||
private $iterator;
|
||||
|
||||
/** @var array */
|
||||
private $excluded = array();
|
||||
|
||||
/**
|
||||
* @param \RecursiveDirectoryIterator $iterator
|
||||
* @param array $excluded
|
||||
*/
|
||||
public function __construct(\RecursiveDirectoryIterator $iterator, array $excluded)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->iterator = $iterator;
|
||||
$this->excluded = array_map(array($this, 'getPathname'), $excluded);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* Check whether the current element of the iterator is acceptable
|
||||
*
|
||||
* @link http://php.net/manual/en/filteriterator.accept.php
|
||||
* @return bool true if the current element is acceptable, otherwise false.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function accept()
|
||||
{
|
||||
$current = $this->current()->getPathname();
|
||||
$current = $this->normalizeDirectorySeparator($current);
|
||||
|
||||
if ('.' . DIRECTORY_SEPARATOR !== $current[0] . $current[1]) {
|
||||
$current = '.' . DIRECTORY_SEPARATOR . $current;
|
||||
}
|
||||
|
||||
return !in_array($current, $this->excluded);
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* Check whether the inner iterator's current element has children
|
||||
*
|
||||
* @link http://php.net/manual/en/recursivefilteriterator.haschildren.php
|
||||
* @return bool true if the inner iterator has children, otherwise false
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function hasChildren()
|
||||
{
|
||||
return $this->iterator->hasChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.1.0)<br/>
|
||||
* Return the inner iterator's children contained in a RecursiveFilterIterator
|
||||
*
|
||||
* @link http://php.net/manual/en/recursivefilteriterator.getchildren.php
|
||||
* @return \RecursiveFilterIterator containing the inner iterator's children.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
public function getChildren()
|
||||
{
|
||||
return new self($this->iterator->getChildren(), $this->excluded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
private function getPathname($file)
|
||||
{
|
||||
$file = $this->normalizeDirectorySeparator($file);
|
||||
|
||||
if ('.' . DIRECTORY_SEPARATOR !== $file[0] . $file[1]) {
|
||||
$file = '.' . DIRECTORY_SEPARATOR . $file;
|
||||
}
|
||||
|
||||
$directoryFile = new \SplFileInfo($file);
|
||||
return $directoryFile->getPathname();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return string
|
||||
*/
|
||||
private function normalizeDirectorySeparator($file)
|
||||
{
|
||||
return str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $file);
|
||||
}
|
||||
}
|
||||
588
contrib/parallel-lint/src/Output.php
Normal file
588
contrib/parallel-lint/src/Output.php
Normal file
@ -0,0 +1,588 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
interface Output
|
||||
{
|
||||
public function __construct(IWriter $writer);
|
||||
|
||||
public function ok();
|
||||
|
||||
public function skip();
|
||||
|
||||
public function error();
|
||||
|
||||
public function fail();
|
||||
|
||||
public function setTotalFileCount($count);
|
||||
|
||||
public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null);
|
||||
|
||||
public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails);
|
||||
}
|
||||
|
||||
class JsonOutput implements Output
|
||||
{
|
||||
/** @var IWriter */
|
||||
protected $writer;
|
||||
|
||||
/** @var int */
|
||||
protected $phpVersion;
|
||||
|
||||
/** @var int */
|
||||
protected $parallelJobs;
|
||||
|
||||
/** @var string */
|
||||
protected $hhvmVersion;
|
||||
|
||||
/**
|
||||
* @param IWriter $writer
|
||||
*/
|
||||
public function __construct(IWriter $writer)
|
||||
{
|
||||
$this->writer = $writer;
|
||||
}
|
||||
|
||||
public function ok()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function skip()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function error()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function fail()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setTotalFileCount($count)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
|
||||
{
|
||||
$this->phpVersion = $phpVersion;
|
||||
$this->parallelJobs = $parallelJobs;
|
||||
$this->hhvmVersion = $hhvmVersion;
|
||||
}
|
||||
|
||||
public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
|
||||
{
|
||||
echo json_encode(array(
|
||||
'phpVersion' => $this->phpVersion,
|
||||
'hhvmVersion' => $this->hhvmVersion,
|
||||
'parallelJobs' => $this->parallelJobs,
|
||||
'results' => $result,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class GitLabOutput implements Output
|
||||
{
|
||||
/** @var IWriter */
|
||||
protected $writer;
|
||||
|
||||
/**
|
||||
* @param IWriter $writer
|
||||
*/
|
||||
public function __construct(IWriter $writer)
|
||||
{
|
||||
$this->writer = $writer;
|
||||
}
|
||||
|
||||
public function ok()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function skip()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function error()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function fail()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setTotalFileCount($count)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
|
||||
{
|
||||
$errors = array();
|
||||
foreach ($result->getErrors() as $error) {
|
||||
$message = $error->getMessage();
|
||||
$line = 1;
|
||||
if ($error instanceof SyntaxError) {
|
||||
$line = $error->getLine();
|
||||
}
|
||||
$filePath = $error->getFilePath();
|
||||
$result = array(
|
||||
'type' => 'issue',
|
||||
'check_name' => 'Parse error',
|
||||
'description' => $message,
|
||||
'categories' => 'Style',
|
||||
'fingerprint' => md5($filePath . $message . $line),
|
||||
'severity' => 'minor',
|
||||
'location' => array(
|
||||
'path' => $filePath,
|
||||
'lines' => array(
|
||||
'begin' => $line,
|
||||
),
|
||||
),
|
||||
);
|
||||
array_push($errors, $result);
|
||||
}
|
||||
|
||||
$string = json_encode($errors) . PHP_EOL;
|
||||
$this->writer->write($string);
|
||||
}
|
||||
}
|
||||
|
||||
class TextOutput implements Output
|
||||
{
|
||||
const TYPE_DEFAULT = 'default',
|
||||
TYPE_SKIP = 'skip',
|
||||
TYPE_ERROR = 'error',
|
||||
TYPE_FAIL = 'fail',
|
||||
TYPE_OK = 'ok';
|
||||
|
||||
/** @var int */
|
||||
public $filesPerLine = 60;
|
||||
|
||||
/** @var bool */
|
||||
public $showProgress = true;
|
||||
|
||||
/** @var int */
|
||||
protected $checkedFiles;
|
||||
|
||||
/** @var int */
|
||||
protected $totalFileCount;
|
||||
|
||||
/** @var IWriter */
|
||||
protected $writer;
|
||||
|
||||
/**
|
||||
* @param IWriter $writer
|
||||
*/
|
||||
public function __construct(IWriter $writer)
|
||||
{
|
||||
$this->writer = $writer;
|
||||
}
|
||||
|
||||
public function ok()
|
||||
{
|
||||
$this->writeMark(self::TYPE_OK);
|
||||
}
|
||||
|
||||
public function skip()
|
||||
{
|
||||
$this->writeMark(self::TYPE_SKIP);
|
||||
}
|
||||
|
||||
public function error()
|
||||
{
|
||||
$this->writeMark(self::TYPE_ERROR);
|
||||
}
|
||||
|
||||
public function fail()
|
||||
{
|
||||
$this->writeMark(self::TYPE_FAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param string $type
|
||||
*/
|
||||
public function write($string, $type = self::TYPE_DEFAULT)
|
||||
{
|
||||
$this->writer->write($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $line
|
||||
* @param string $type
|
||||
*/
|
||||
public function writeLine($line = null, $type = self::TYPE_DEFAULT)
|
||||
{
|
||||
$this->write($line, $type);
|
||||
$this->writeNewLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*/
|
||||
public function writeNewLine($count = 1)
|
||||
{
|
||||
$this->write(str_repeat(PHP_EOL, $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $count
|
||||
*/
|
||||
public function setTotalFileCount($count)
|
||||
{
|
||||
$this->totalFileCount = $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $phpVersion
|
||||
* @param int $parallelJobs
|
||||
* @param string $hhvmVersion
|
||||
*/
|
||||
public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
|
||||
{
|
||||
$this->write("PHP {$this->phpVersionIdToString($phpVersion)} | ");
|
||||
|
||||
if ($hhvmVersion) {
|
||||
$this->write("HHVM $hhvmVersion | ");
|
||||
}
|
||||
|
||||
if ($parallelJobs === 1) {
|
||||
$this->writeLine("1 job");
|
||||
} else {
|
||||
$this->writeLine("{$parallelJobs} parallel jobs");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Result $result
|
||||
* @param ErrorFormatter $errorFormatter
|
||||
* @param bool $ignoreFails
|
||||
*/
|
||||
public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
|
||||
{
|
||||
if ($this->showProgress) {
|
||||
if ($this->checkedFiles % $this->filesPerLine !== 0) {
|
||||
$rest = $this->filesPerLine - ($this->checkedFiles % $this->filesPerLine);
|
||||
$this->write(str_repeat(' ', $rest));
|
||||
$this->writePercent();
|
||||
}
|
||||
|
||||
$this->writeNewLine(2);
|
||||
}
|
||||
|
||||
$testTime = round($result->getTestTime(), 1);
|
||||
$message = "Checked {$result->getCheckedFilesCount()} files in $testTime ";
|
||||
$message .= $testTime == 1 ? 'second' : 'seconds';
|
||||
|
||||
if ($result->getSkippedFilesCount() > 0) {
|
||||
$message .= ", skipped {$result->getSkippedFilesCount()} ";
|
||||
$message .= ($result->getSkippedFilesCount() === 1 ? 'file' : 'files');
|
||||
}
|
||||
|
||||
$this->writeLine($message);
|
||||
|
||||
if (!$result->hasSyntaxError()) {
|
||||
$message = "No syntax error found";
|
||||
} else {
|
||||
$message = "Syntax error found in {$result->getFilesWithSyntaxErrorCount()} ";
|
||||
$message .= ($result->getFilesWithSyntaxErrorCount() === 1 ? 'file' : 'files');
|
||||
}
|
||||
|
||||
if ($result->hasFilesWithFail()) {
|
||||
$message .= ", failed to check {$result->getFilesWithFailCount()} ";
|
||||
$message .= ($result->getFilesWithFailCount() === 1 ? 'file' : 'files');
|
||||
|
||||
if ($ignoreFails) {
|
||||
$message .= ' (ignored)';
|
||||
}
|
||||
}
|
||||
|
||||
$hasError = $ignoreFails ? $result->hasSyntaxError() : $result->hasError();
|
||||
$this->writeLine($message, $hasError ? self::TYPE_ERROR : self::TYPE_OK);
|
||||
|
||||
if ($result->hasError()) {
|
||||
$this->writeNewLine();
|
||||
foreach ($result->getErrors() as $error) {
|
||||
$this->writeLine(str_repeat('-', 60));
|
||||
$this->writeLine($errorFormatter->format($error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function writeMark($type)
|
||||
{
|
||||
++$this->checkedFiles;
|
||||
|
||||
if ($this->showProgress) {
|
||||
if ($type === self::TYPE_OK) {
|
||||
$this->writer->write('.');
|
||||
|
||||
} else if ($type === self::TYPE_SKIP) {
|
||||
$this->write('S', self::TYPE_SKIP);
|
||||
|
||||
} else if ($type === self::TYPE_ERROR) {
|
||||
$this->write('X', self::TYPE_ERROR);
|
||||
|
||||
} else if ($type === self::TYPE_FAIL) {
|
||||
$this->writer->write('-');
|
||||
}
|
||||
|
||||
if ($this->checkedFiles % $this->filesPerLine === 0) {
|
||||
$this->writePercent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function writePercent()
|
||||
{
|
||||
$percent = floor($this->checkedFiles / $this->totalFileCount * 100);
|
||||
$current = $this->stringWidth($this->checkedFiles, strlen($this->totalFileCount));
|
||||
$this->writeLine(" $current/$this->totalFileCount ($percent %)");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @param int $width
|
||||
* @return string
|
||||
*/
|
||||
protected function stringWidth($input, $width = 3)
|
||||
{
|
||||
$multiplier = $width - strlen($input);
|
||||
return str_repeat(' ', $multiplier > 0 ? $multiplier : 0) . $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $phpVersionId
|
||||
* @return string
|
||||
*/
|
||||
protected function phpVersionIdToString($phpVersionId)
|
||||
{
|
||||
$releaseVersion = (int) substr($phpVersionId, -2, 2);
|
||||
$minorVersion = (int) substr($phpVersionId, -4, 2);
|
||||
$majorVersion = (int) substr($phpVersionId, 0, strlen($phpVersionId) - 4);
|
||||
|
||||
return "$majorVersion.$minorVersion.$releaseVersion";
|
||||
}
|
||||
}
|
||||
|
||||
class CheckstyleOutput implements Output
|
||||
{
|
||||
private $writer;
|
||||
|
||||
public function __construct(IWriter $writer)
|
||||
{
|
||||
$this->writer = $writer;
|
||||
}
|
||||
|
||||
public function ok()
|
||||
{
|
||||
}
|
||||
|
||||
public function skip()
|
||||
{
|
||||
}
|
||||
|
||||
public function error()
|
||||
{
|
||||
}
|
||||
|
||||
public function fail()
|
||||
{
|
||||
}
|
||||
|
||||
public function setTotalFileCount($count)
|
||||
{
|
||||
}
|
||||
|
||||
public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
|
||||
{
|
||||
$this->writer->write('<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL);
|
||||
}
|
||||
|
||||
public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
|
||||
{
|
||||
$this->writer->write('<checkstyle>' . PHP_EOL);
|
||||
$errors = array();
|
||||
|
||||
foreach ($result->getErrors() as $error) {
|
||||
$message = $error->getMessage();
|
||||
if ($error instanceof SyntaxError) {
|
||||
$line = $error->getLine();
|
||||
$source = "Syntax Error";
|
||||
} else {
|
||||
$line = 1;
|
||||
$source = "Linter Error";
|
||||
}
|
||||
|
||||
$errors[$error->getShortFilePath()][] = array(
|
||||
'message' => $message,
|
||||
'line' => $line,
|
||||
'source' => $source
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($errors as $file => $fileErrors) {
|
||||
$this->writer->write(sprintf(' <file name="%s">', $file) . PHP_EOL);
|
||||
foreach ($fileErrors as $fileError) {
|
||||
$this->writer->write(
|
||||
sprintf(
|
||||
' <error line="%d" severity="ERROR" message="%s" source="%s" />',
|
||||
$fileError['line'],
|
||||
$fileError['message'],
|
||||
$fileError['source']
|
||||
) .
|
||||
PHP_EOL
|
||||
);
|
||||
}
|
||||
$this->writer->write(' </file>' . PHP_EOL);
|
||||
}
|
||||
|
||||
$this->writer->write('</checkstyle>' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
class TextOutputColored extends TextOutput
|
||||
{
|
||||
/** @var \JakubOnderka\PhpConsoleColor\ConsoleColor */
|
||||
private $colors;
|
||||
|
||||
public function __construct(IWriter $writer, $colors = Settings::AUTODETECT)
|
||||
{
|
||||
parent::__construct($writer);
|
||||
|
||||
if (class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')) {
|
||||
$this->colors = new \JakubOnderka\PhpConsoleColor\ConsoleColor();
|
||||
$this->colors->setForceStyle($colors === Settings::FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param string $type
|
||||
* @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
|
||||
*/
|
||||
public function write($string, $type = self::TYPE_DEFAULT)
|
||||
{
|
||||
if (!$this->colors instanceof \JakubOnderka\PhpConsoleColor\ConsoleColor) {
|
||||
parent::write($string, $type);
|
||||
} else {
|
||||
switch ($type) {
|
||||
case self::TYPE_OK:
|
||||
parent::write($this->colors->apply('bg_green', $string));
|
||||
break;
|
||||
|
||||
case self::TYPE_SKIP:
|
||||
parent::write($this->colors->apply('bg_yellow', $string));
|
||||
break;
|
||||
|
||||
case self::TYPE_ERROR:
|
||||
parent::write($this->colors->apply('bg_red', $string));
|
||||
break;
|
||||
|
||||
default:
|
||||
parent::write($string);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IWriter
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
*/
|
||||
public function write($string);
|
||||
}
|
||||
|
||||
class NullWriter implements IWriter
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class ConsoleWriter implements IWriter
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
echo $string;
|
||||
}
|
||||
}
|
||||
|
||||
class FileWriter implements IWriter
|
||||
{
|
||||
/** @var string */
|
||||
protected $logFile;
|
||||
|
||||
/** @var string */
|
||||
protected $buffer;
|
||||
|
||||
public function __construct($logFile)
|
||||
{
|
||||
$this->logFile = $logFile;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
$this->buffer .= $string;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
file_put_contents($this->logFile, $this->buffer);
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleWriter implements IWriter
|
||||
{
|
||||
/** @var IWriter[] */
|
||||
protected $writers;
|
||||
|
||||
/**
|
||||
* @param IWriter[] $writers
|
||||
*/
|
||||
public function __construct(array $writers)
|
||||
{
|
||||
foreach ($writers as $writer) {
|
||||
$this->addWriter($writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IWriter $writer
|
||||
*/
|
||||
public function addWriter(IWriter $writer)
|
||||
{
|
||||
$this->writers[] = $writer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
*/
|
||||
public function write($string)
|
||||
{
|
||||
foreach ($this->writers as $writer) {
|
||||
$writer->write($string);
|
||||
}
|
||||
}
|
||||
}
|
||||
286
contrib/parallel-lint/src/ParallelLint.php
Normal file
286
contrib/parallel-lint/src/ParallelLint.php
Normal file
@ -0,0 +1,286 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
|
||||
use JakubOnderka\PhpParallelLint\Process\LintProcess;
|
||||
use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
|
||||
use JakubOnderka\PhpParallelLint\Process\SkipLintProcess;
|
||||
|
||||
class ParallelLint
|
||||
{
|
||||
const STATUS_OK = 'ok',
|
||||
STATUS_SKIP = 'skip',
|
||||
STATUS_FAIL = 'fail',
|
||||
STATUS_ERROR = 'error';
|
||||
|
||||
/** @var int */
|
||||
private $parallelJobs;
|
||||
|
||||
/** @var PhpExecutable */
|
||||
private $phpExecutable;
|
||||
|
||||
/** @var bool */
|
||||
private $aspTagsEnabled = false;
|
||||
|
||||
/** @var bool */
|
||||
private $shortTagEnabled = false;
|
||||
|
||||
/** @var callable */
|
||||
private $processCallback;
|
||||
|
||||
/** @var bool */
|
||||
private $showDeprecated = false;
|
||||
|
||||
/** @var SyntaxErrorCallback|null */
|
||||
private $syntaxErrorCallback = null;
|
||||
|
||||
public function __construct(PhpExecutable $phpExecutable, $parallelJobs = 10)
|
||||
{
|
||||
$this->phpExecutable = $phpExecutable;
|
||||
$this->parallelJobs = $parallelJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $files
|
||||
* @return Result
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function lint(array $files)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
$skipLintProcess = new SkipLintProcess($this->phpExecutable, $files);
|
||||
|
||||
$processCallback = is_callable($this->processCallback) ? $this->processCallback : function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* @var LintProcess[] $running
|
||||
* @var LintProcess[] $waiting
|
||||
*/
|
||||
$errors = $running = $waiting = array();
|
||||
$skippedFiles = $checkedFiles = array();
|
||||
|
||||
while ($files || $running) {
|
||||
for ($i = count($running); $files && $i < $this->parallelJobs; $i++) {
|
||||
$file = array_shift($files);
|
||||
|
||||
if ($skipLintProcess->isSkipped($file) === true) {
|
||||
$skippedFiles[] = $file;
|
||||
$processCallback(self::STATUS_SKIP, $file);
|
||||
} else {
|
||||
$running[$file] = new LintProcess(
|
||||
$this->phpExecutable,
|
||||
$file,
|
||||
$this->aspTagsEnabled,
|
||||
$this->shortTagEnabled,
|
||||
$this->showDeprecated
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$skipLintProcess->getChunk();
|
||||
usleep(100);
|
||||
|
||||
foreach ($running as $file => $process) {
|
||||
if ($process->isFinished()) {
|
||||
unset($running[$file]);
|
||||
|
||||
$skipStatus = $skipLintProcess->isSkipped($file);
|
||||
if ($skipStatus === null) {
|
||||
$waiting[$file] = $process;
|
||||
|
||||
} else if ($skipStatus === true) {
|
||||
$skippedFiles[] = $file;
|
||||
$processCallback(self::STATUS_SKIP, $file);
|
||||
|
||||
} else if ($process->containsError()) {
|
||||
$checkedFiles[] = $file;
|
||||
$errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
|
||||
$processCallback(self::STATUS_ERROR, $file);
|
||||
|
||||
} else if ($process->isSuccess()) {
|
||||
$checkedFiles[] = $file;
|
||||
$processCallback(self::STATUS_OK, $file);
|
||||
|
||||
|
||||
} else {
|
||||
$errors[] = new Error($file, $process->getOutput());
|
||||
$processCallback(self::STATUS_FAIL, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($waiting)) {
|
||||
$skipLintProcess->waitForFinish();
|
||||
|
||||
if ($skipLintProcess->isFail()) {
|
||||
$message = "Error in skip-linting.php process\nError output: {$skipLintProcess->getErrorOutput()}";
|
||||
throw new \Exception($message);
|
||||
}
|
||||
|
||||
foreach ($waiting as $file => $process) {
|
||||
$skipStatus = $skipLintProcess->isSkipped($file);
|
||||
if ($skipStatus === null) {
|
||||
throw new \Exception("File $file has empty skip status. Please contact the author of PHP Parallel Lint.");
|
||||
|
||||
} else if ($skipStatus === true) {
|
||||
$skippedFiles[] = $file;
|
||||
$processCallback(self::STATUS_SKIP, $file);
|
||||
|
||||
} else if ($process->isSuccess()) {
|
||||
$checkedFiles[] = $file;
|
||||
$processCallback(self::STATUS_OK, $file);
|
||||
|
||||
} else if ($process->containsError()) {
|
||||
$checkedFiles[] = $file;
|
||||
$errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
|
||||
$processCallback(self::STATUS_ERROR, $file);
|
||||
|
||||
} else {
|
||||
$errors[] = new Error($file, $process->getOutput());
|
||||
$processCallback(self::STATUS_FAIL, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$testTime = microtime(true) - $startTime;
|
||||
|
||||
return new Result($errors, $checkedFiles, $skippedFiles, $testTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getParallelJobs()
|
||||
{
|
||||
return $this->parallelJobs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $parallelJobs
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setParallelJobs($parallelJobs)
|
||||
{
|
||||
$this->parallelJobs = $parallelJobs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPhpExecutable()
|
||||
{
|
||||
return $this->phpExecutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $phpExecutable
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setPhpExecutable($phpExecutable)
|
||||
{
|
||||
$this->phpExecutable = $phpExecutable;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getProcessCallback()
|
||||
{
|
||||
return $this->processCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $processCallback
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setProcessCallback($processCallback)
|
||||
{
|
||||
$this->processCallback = $processCallback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isAspTagsEnabled()
|
||||
{
|
||||
return $this->aspTagsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $aspTagsEnabled
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setAspTagsEnabled($aspTagsEnabled)
|
||||
{
|
||||
$this->aspTagsEnabled = $aspTagsEnabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isShortTagEnabled()
|
||||
{
|
||||
return $this->shortTagEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $shortTagEnabled
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setShortTagEnabled($shortTagEnabled)
|
||||
{
|
||||
$this->shortTagEnabled = $shortTagEnabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isShowDeprecated()
|
||||
{
|
||||
return $this->showDeprecated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $showDeprecated
|
||||
* @return ParallelLint
|
||||
*/
|
||||
public function setShowDeprecated($showDeprecated)
|
||||
{
|
||||
$this->showDeprecated = $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;
|
||||
}
|
||||
}
|
||||
147
contrib/parallel-lint/src/Process/GitBlameProcess.php
Normal file
147
contrib/parallel-lint/src/Process/GitBlameProcess.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\RunTimeException;
|
||||
|
||||
class GitBlameProcess extends Process
|
||||
{
|
||||
/**
|
||||
* @param string $gitExecutable
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function __construct($gitExecutable, $file, $line)
|
||||
{
|
||||
$arguments = array('blame', '-p', '-L', "$line,+1", $file);
|
||||
parent::__construct($gitExecutable, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function isSuccess()
|
||||
{
|
||||
return $this->getStatusCode() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
if (!$this->isSuccess()) {
|
||||
throw new RunTimeException("Author can only be retrieved for successful process output.");
|
||||
}
|
||||
|
||||
$output = $this->getOutput();
|
||||
preg_match('~^author (.*)~m', $output, $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getAuthorEmail()
|
||||
{
|
||||
if (!$this->isSuccess()) {
|
||||
throw new RunTimeException("Author e-mail can only be retrieved for successful process output.");
|
||||
}
|
||||
|
||||
$output = $this->getOutput();
|
||||
preg_match('~^author-mail <(.*)>~m', $output, $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTime
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getAuthorTime()
|
||||
{
|
||||
if (!$this->isSuccess()) {
|
||||
throw new RunTimeException("Author time can only be retrieved for successful process output.");
|
||||
}
|
||||
|
||||
$output = $this->getOutput();
|
||||
|
||||
preg_match('~^author-time (.*)~m', $output, $matches);
|
||||
$time = $matches[1];
|
||||
|
||||
preg_match('~^author-tz (.*)~m', $output, $matches);
|
||||
$zone = $matches[1];
|
||||
|
||||
return $this->getDateTime($time, $zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getCommitHash()
|
||||
{
|
||||
if (!$this->isSuccess()) {
|
||||
throw new RunTimeException("Commit hash can only be retrieved for successful process output.");
|
||||
}
|
||||
|
||||
return substr($this->getOutput(), 0, strpos($this->getOutput(), ' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
if (!$this->isSuccess()) {
|
||||
throw new RunTimeException("Commit summary can only be retrieved for successful process output.");
|
||||
}
|
||||
|
||||
$output = $this->getOutput();
|
||||
preg_match('~^summary (.*)~m', $output, $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $gitExecutable
|
||||
* @return bool
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public static function gitExists($gitExecutable)
|
||||
{
|
||||
$process = new Process($gitExecutable, array('--version'));
|
||||
$process->waitForFinish();
|
||||
return $process->getStatusCode() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This harakiri method is required to correct support time zone in PHP 5.4
|
||||
*
|
||||
* @param int $time
|
||||
* @param string $zone
|
||||
* @return \DateTime
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getDateTime($time, $zone)
|
||||
{
|
||||
$utcTimeZone = new \DateTimeZone('UTC');
|
||||
$datetime = \DateTime::createFromFormat('U', $time, $utcTimeZone);
|
||||
|
||||
$way = substr($zone, 0, 1);
|
||||
$hours = (int) substr($zone, 1, 2);
|
||||
$minutes = (int) substr($zone, 3, 2);
|
||||
|
||||
$interval = new \DateInterval("PT{$hours}H{$minutes}M");
|
||||
|
||||
if ($way === '+') {
|
||||
$datetime->add($interval);
|
||||
} else {
|
||||
$datetime->sub($interval);
|
||||
}
|
||||
|
||||
return new \DateTime($datetime->format('Y-m-d\TH:i:s') . $zone, $utcTimeZone);
|
||||
}
|
||||
}
|
||||
137
contrib/parallel-lint/src/Process/LintProcess.php
Normal file
137
contrib/parallel-lint/src/Process/LintProcess.php
Normal file
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\RunTimeException;
|
||||
|
||||
class LintProcess extends PhpProcess
|
||||
{
|
||||
const FATAL_ERROR = 'Fatal error';
|
||||
const PARSE_ERROR = 'Parse error';
|
||||
const DEPRECATED_ERROR = 'Deprecated:';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $showDeprecatedErrors;
|
||||
|
||||
/**
|
||||
* @param PhpExecutable $phpExecutable
|
||||
* @param string $fileToCheck Path to file to check
|
||||
* @param bool $aspTags
|
||||
* @param bool $shortTag
|
||||
* @param bool $deprecated
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function __construct(PhpExecutable $phpExecutable, $fileToCheck, $aspTags = false, $shortTag = false, $deprecated = false)
|
||||
{
|
||||
if (empty($fileToCheck)) {
|
||||
throw new \InvalidArgumentException("File to check must be set.");
|
||||
}
|
||||
|
||||
$parameters = array(
|
||||
'-d asp_tags=' . ($aspTags ? 'On' : 'Off'),
|
||||
'-d short_open_tag=' . ($shortTag ? 'On' : 'Off'),
|
||||
'-d error_reporting=E_ALL',
|
||||
'-n',
|
||||
'-l',
|
||||
$fileToCheck,
|
||||
);
|
||||
|
||||
$this->showDeprecatedErrors = $deprecated;
|
||||
parent::__construct($phpExecutable, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws
|
||||
*/
|
||||
public function containsError()
|
||||
{
|
||||
return $this->containsParserError($this->getOutput()) ||
|
||||
$this->containsFatalError($this->getOutput()) ||
|
||||
$this->containsDeprecatedError($this->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getSyntaxError()
|
||||
{
|
||||
if ($this->containsError()) {
|
||||
// Look for fatal errors first
|
||||
foreach (explode("\n", $this->getOutput()) as $line) {
|
||||
if ($this->containsFatalError($line)) {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for parser errors second
|
||||
foreach (explode("\n", $this->getOutput()) as $line) {
|
||||
if ($this->containsParserError($line)) {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for deprecated errors third
|
||||
foreach (explode("\n", $this->getOutput()) as $line) {
|
||||
if ($this->containsDeprecatedError($line)) {
|
||||
return $line;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RunTimeException("The output '{$this->getOutput()}' does not contain Parse or Syntax errors");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function isFail()
|
||||
{
|
||||
return defined('PHP_WINDOWS_VERSION_MAJOR') ? $this->getStatusCode() === 1 : parent::isFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function isSuccess()
|
||||
{
|
||||
return $this->getStatusCode() === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function containsParserError($string)
|
||||
{
|
||||
return strpos($string, self::PARSE_ERROR) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function containsFatalError($string)
|
||||
{
|
||||
return strpos($string, self::FATAL_ERROR) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function containsDeprecatedError($string)
|
||||
{
|
||||
if ($this->showDeprecatedErrors === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return strpos($string, self::DEPRECATED_ERROR) !== false;
|
||||
}
|
||||
}
|
||||
128
contrib/parallel-lint/src/Process/PhpExecutable.php
Normal file
128
contrib/parallel-lint/src/Process/PhpExecutable.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\RunTimeException;
|
||||
|
||||
class PhpExecutable
|
||||
{
|
||||
/** @var string */
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Version as PHP_VERSION_ID constant
|
||||
* @var int
|
||||
*/
|
||||
private $versionId;
|
||||
|
||||
/** @var string */
|
||||
private $hhvmVersion;
|
||||
|
||||
/** @var bool */
|
||||
private $isHhvmType;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param int $versionId
|
||||
* @param string $hhvmVersion
|
||||
* @param bool $isHhvmType
|
||||
*/
|
||||
public function __construct($path, $versionId, $hhvmVersion, $isHhvmType)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->versionId = $versionId;
|
||||
$this->hhvmVersion = $hhvmVersion;
|
||||
$this->isHhvmType = $isHhvmType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHhvmVersion()
|
||||
{
|
||||
return $this->hhvmVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isIsHhvmType()
|
||||
{
|
||||
return $this->isHhvmType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersionId()
|
||||
{
|
||||
return $this->versionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $phpExecutable
|
||||
* @return PhpExecutable
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPhpExecutable($phpExecutable)
|
||||
{
|
||||
$codeToExecute = <<<PHP
|
||||
echo 'PHP;', PHP_VERSION_ID, ';', defined('HPHP_VERSION') ? HPHP_VERSION : null;
|
||||
PHP;
|
||||
|
||||
$process = new Process($phpExecutable, array('-n', '-r', $codeToExecute));
|
||||
$process->waitForFinish();
|
||||
|
||||
try {
|
||||
if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
|
||||
throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
|
||||
}
|
||||
|
||||
return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput());
|
||||
|
||||
} catch (RunTimeException $e) {
|
||||
// Try HHVM type
|
||||
$process = new Process($phpExecutable, array('--php', '-r', $codeToExecute));
|
||||
$process->waitForFinish();
|
||||
|
||||
if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
|
||||
throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
|
||||
}
|
||||
|
||||
return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput(), $isHhvmType = true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $phpExecutable
|
||||
* @param string $output
|
||||
* @param bool $isHhvmType
|
||||
* @return PhpExecutable
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
private static function getPhpExecutableFromOutput($phpExecutable, $output, $isHhvmType = false)
|
||||
{
|
||||
$parts = explode(';', $output);
|
||||
|
||||
if ($parts[0] !== 'PHP' || !preg_match('~([0-9]+)~', $parts[1], $matches)) {
|
||||
throw new RunTimeException("'{$phpExecutable}' is not valid PHP binary.");
|
||||
}
|
||||
|
||||
$hhvmVersion = isset($parts[2]) ? $parts[2] : false;
|
||||
|
||||
return new PhpExecutable(
|
||||
$phpExecutable,
|
||||
intval($matches[1]),
|
||||
$hhvmVersion,
|
||||
$isHhvmType
|
||||
);
|
||||
}
|
||||
}
|
||||
35
contrib/parallel-lint/src/Process/PhpProcess.php
Normal file
35
contrib/parallel-lint/src/Process/PhpProcess.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
class PhpProcess extends Process
|
||||
{
|
||||
/**
|
||||
* @param PhpExecutable $phpExecutable
|
||||
* @param array $parameters
|
||||
* @param string|null $stdIn
|
||||
* @throws \JakubOnderka\PhpParallelLint\RunTimeException
|
||||
*/
|
||||
public function __construct(PhpExecutable $phpExecutable, array $parameters = array(), $stdIn = null)
|
||||
{
|
||||
$constructedParameters = $this->constructParameters($parameters, $phpExecutable->isIsHhvmType());
|
||||
parent::__construct($phpExecutable->getPath(), $constructedParameters, $stdIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $parameters
|
||||
* @param bool $isHhvm
|
||||
* @return array
|
||||
*/
|
||||
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) {
|
||||
array_unshift($parameters, '-php');
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
}
|
||||
}
|
||||
153
contrib/parallel-lint/src/Process/Process.php
Normal file
153
contrib/parallel-lint/src/Process/Process.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\RunTimeException;
|
||||
|
||||
class Process
|
||||
{
|
||||
const STDIN = 0,
|
||||
STDOUT = 1,
|
||||
STDERR = 2;
|
||||
|
||||
const READ = 'r',
|
||||
WRITE = 'w';
|
||||
|
||||
/** @var resource */
|
||||
protected $process;
|
||||
|
||||
/** @var resource */
|
||||
protected $stdout;
|
||||
|
||||
/** @var resource */
|
||||
protected $stderr;
|
||||
|
||||
/** @var string */
|
||||
private $output;
|
||||
|
||||
/** @var string */
|
||||
private $errorOutput;
|
||||
|
||||
/** @var int */
|
||||
private $statusCode;
|
||||
|
||||
/**
|
||||
* @param string $executable
|
||||
* @param string[] $arguments
|
||||
* @param string $stdInInput
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function __construct($executable, array $arguments = array(), $stdInInput = null)
|
||||
{
|
||||
$descriptors = array(
|
||||
self::STDIN => array('pipe', self::READ),
|
||||
self::STDOUT => array('pipe', self::WRITE),
|
||||
self::STDERR => array('pipe', self::WRITE),
|
||||
);
|
||||
|
||||
$cmdLine = $executable . ' ' . implode(' ', array_map('escapeshellarg', $arguments));
|
||||
$this->process = proc_open($cmdLine, $descriptors, $pipes, null, null, array('bypass_shell' => true));
|
||||
|
||||
if ($this->process === false || $this->process === null) {
|
||||
throw new RunTimeException("Cannot create new process $cmdLine");
|
||||
}
|
||||
|
||||
list($stdin, $this->stdout, $this->stderr) = $pipes;
|
||||
|
||||
if ($stdInInput) {
|
||||
fwrite($stdin, $stdInInput);
|
||||
}
|
||||
|
||||
fclose($stdin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFinished()
|
||||
{
|
||||
if ($this->statusCode !== null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$status = proc_get_status($this->process);
|
||||
|
||||
if ($status['running']) {
|
||||
return false;
|
||||
} else if ($this->statusCode === null) {
|
||||
$this->statusCode = (int) $status['exitcode'];
|
||||
}
|
||||
|
||||
// Process outputs
|
||||
$this->output = stream_get_contents($this->stdout);
|
||||
fclose($this->stdout);
|
||||
|
||||
$this->errorOutput = stream_get_contents($this->stderr);
|
||||
fclose($this->stderr);
|
||||
|
||||
$statusCode = proc_close($this->process);
|
||||
|
||||
if ($this->statusCode === null) {
|
||||
$this->statusCode = $statusCode;
|
||||
}
|
||||
|
||||
$this->process = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function waitForFinish()
|
||||
{
|
||||
while (!$this->isFinished()) {
|
||||
usleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
if (!$this->isFinished()) {
|
||||
throw new RunTimeException("Cannot get output for running process");
|
||||
}
|
||||
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getErrorOutput()
|
||||
{
|
||||
if (!$this->isFinished()) {
|
||||
throw new RunTimeException("Cannot get error output for running process");
|
||||
}
|
||||
|
||||
return $this->errorOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
if (!$this->isFinished()) {
|
||||
throw new RunTimeException("Cannot get status code for running process");
|
||||
}
|
||||
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function isFail()
|
||||
{
|
||||
return $this->getStatusCode() === 1;
|
||||
}
|
||||
}
|
||||
91
contrib/parallel-lint/src/Process/SkipLintProcess.php
Normal file
91
contrib/parallel-lint/src/Process/SkipLintProcess.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint\Process;
|
||||
|
||||
use JakubOnderka\PhpParallelLint\RunTimeException;
|
||||
|
||||
class SkipLintProcess extends PhpProcess
|
||||
{
|
||||
/** @var array */
|
||||
private $skipped = array();
|
||||
|
||||
/** @var bool */
|
||||
private $done = false;
|
||||
|
||||
/** @var string */
|
||||
private $endLastChunk = '';
|
||||
|
||||
/**
|
||||
* @param PhpExecutable $phpExecutable
|
||||
* @param array $filesToCheck
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function __construct(PhpExecutable $phpExecutable, array $filesToCheck)
|
||||
{
|
||||
$scriptPath = __DIR__ . '/../../bin/skip-linting.php';
|
||||
$script = file_get_contents($scriptPath);
|
||||
|
||||
if (!$script) {
|
||||
throw new RunTimeException("skip-linting.php script not found in '$scriptPath'.");
|
||||
}
|
||||
|
||||
$script = str_replace('<?php', '', $script);
|
||||
|
||||
$parameters = array('-d', 'display_errors=stderr', '-r', $script);
|
||||
parent::__construct($phpExecutable, $parameters, implode(PHP_EOL, $filesToCheck));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RunTimeException
|
||||
*/
|
||||
public function getChunk()
|
||||
{
|
||||
if (!$this->isFinished()) {
|
||||
$this->processLines(fread($this->stdout, 8192));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \JakubOnderka\PhpParallelLint\RunTimeException
|
||||
*/
|
||||
public function isFinished()
|
||||
{
|
||||
$isFinished = parent::isFinished();
|
||||
if ($isFinished && !$this->done) {
|
||||
$this->done = true;
|
||||
$output = $this->getOutput();
|
||||
$this->processLines($output);
|
||||
}
|
||||
|
||||
return $isFinished;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @return bool|null
|
||||
*/
|
||||
public function isSkipped($file)
|
||||
{
|
||||
if (isset($this->skipped[$file])) {
|
||||
return $this->skipped[$file];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*/
|
||||
private function processLines($content)
|
||||
{
|
||||
if (!empty($content)) {
|
||||
$lines = explode(PHP_EOL, $this->endLastChunk . $content);
|
||||
$this->endLastChunk = array_pop($lines);
|
||||
foreach ($lines as $line) {
|
||||
$parts = explode(';', $line);
|
||||
list($file, $status) = $parts;
|
||||
$this->skipped[$file] = $status === '1' ? true : false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
contrib/parallel-lint/src/Result.php
Normal file
171
contrib/parallel-lint/src/Result.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class Result implements \JsonSerializable
|
||||
{
|
||||
/** @var Error[] */
|
||||
private $errors;
|
||||
|
||||
/** @var array */
|
||||
private $checkedFiles;
|
||||
|
||||
/** @var array */
|
||||
private $skippedFiles;
|
||||
|
||||
/** @var float */
|
||||
private $testTime;
|
||||
|
||||
/**
|
||||
* @param Error[] $errors
|
||||
* @param array $checkedFiles
|
||||
* @param array $skippedFiles
|
||||
* @param float $testTime
|
||||
*/
|
||||
public function __construct(array $errors, array $checkedFiles, array $skippedFiles, $testTime)
|
||||
{
|
||||
$this->errors = $errors;
|
||||
$this->checkedFiles = $checkedFiles;
|
||||
$this->skippedFiles = $skippedFiles;
|
||||
$this->testTime = $testTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Error[]
|
||||
*/
|
||||
public function getErrors()
|
||||
{
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasError()
|
||||
{
|
||||
return !empty($this->errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesWithFail()
|
||||
{
|
||||
$filesWithFail = array();
|
||||
foreach ($this->errors as $error) {
|
||||
if (!$error instanceof SyntaxError) {
|
||||
$filesWithFail[] = $error->getFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
return $filesWithFail;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFilesWithFailCount()
|
||||
{
|
||||
return count($this->getFilesWithFail());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFilesWithFail()
|
||||
{
|
||||
return $this->getFilesWithFailCount() !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCheckedFiles()
|
||||
{
|
||||
return $this->checkedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCheckedFilesCount()
|
||||
{
|
||||
return count($this->checkedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSkippedFiles()
|
||||
{
|
||||
return $this->skippedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSkippedFilesCount()
|
||||
{
|
||||
return count($this->skippedFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilesWithSyntaxError()
|
||||
{
|
||||
$filesWithSyntaxError = array();
|
||||
foreach ($this->errors as $error) {
|
||||
if ($error instanceof SyntaxError) {
|
||||
$filesWithSyntaxError[] = $error->getFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
return $filesWithSyntaxError;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getFilesWithSyntaxErrorCount()
|
||||
{
|
||||
return count($this->getFilesWithSyntaxError());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSyntaxError()
|
||||
{
|
||||
return $this->getFilesWithSyntaxErrorCount() !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getTestTime()
|
||||
{
|
||||
return $this->testTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* (PHP 5 >= 5.4.0)<br/>
|
||||
* Specify data which should be serialized to JSON
|
||||
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
|
||||
* @return mixed data which can be serialized by <b>json_encode</b>,
|
||||
* which is a value of any type other than a resource.
|
||||
*/
|
||||
#[ReturnTypeWillChange]
|
||||
function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'checkedFiles' => $this->getCheckedFiles(),
|
||||
'filesWithSyntaxError' => $this->getFilesWithSyntaxError(),
|
||||
'skippedFiles' => $this->getSkippedFiles(),
|
||||
'errors' => $this->getErrors(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
247
contrib/parallel-lint/src/Settings.php
Normal file
247
contrib/parallel-lint/src/Settings.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
class Settings
|
||||
{
|
||||
|
||||
/**
|
||||
* constants for enum settings
|
||||
*/
|
||||
const FORCED = 'FORCED';
|
||||
const DISABLED = 'DISABLED';
|
||||
const AUTODETECT = 'AUTODETECT';
|
||||
|
||||
const FORMAT_TEXT = 'text';
|
||||
const FORMAT_JSON = 'json';
|
||||
const FORMAT_GITLAB = 'gitlab';
|
||||
const FORMAT_CHECKSTYLE = 'checkstyle';
|
||||
|
||||
/**
|
||||
* Path to PHP executable
|
||||
* @var string
|
||||
*/
|
||||
public $phpExecutable = 'php';
|
||||
|
||||
/**
|
||||
* Check code inside PHP opening short tag <? or <?= in PHP 5.3
|
||||
* @var bool
|
||||
*/
|
||||
public $shortTag = false;
|
||||
|
||||
/**
|
||||
* Check PHP code inside ASP-style <% %> tags.
|
||||
* @var bool
|
||||
*/
|
||||
public $aspTags = false;
|
||||
|
||||
/**
|
||||
* Number of jobs running in same time
|
||||
* @var int
|
||||
*/
|
||||
public $parallelJobs = 10;
|
||||
|
||||
/**
|
||||
* If path contains directory, only file with these extensions are checked
|
||||
* @var array
|
||||
*/
|
||||
public $extensions = array('php', 'phtml', 'php3', 'php4', 'php5', 'phpt');
|
||||
|
||||
/**
|
||||
* Array of file or directories to check
|
||||
* @var array
|
||||
*/
|
||||
public $paths = array();
|
||||
|
||||
/**
|
||||
* Don't check files or directories
|
||||
* @var array
|
||||
*/
|
||||
public $excluded = array();
|
||||
|
||||
/**
|
||||
* Mode for color detection. Possible values: self::FORCED, self::DISABLED and self::AUTODETECT
|
||||
* @var string
|
||||
*/
|
||||
public $colors = self::AUTODETECT;
|
||||
|
||||
/**
|
||||
* Show progress in text output
|
||||
* @var bool
|
||||
*/
|
||||
public $showProgress = true;
|
||||
|
||||
/**
|
||||
* Output format (see FORMAT_* constants)
|
||||
* @var string
|
||||
*/
|
||||
public $format = self::FORMAT_TEXT;
|
||||
|
||||
/**
|
||||
* Read files and folder to tests from standard input (blocking)
|
||||
* @var bool
|
||||
*/
|
||||
public $stdin = false;
|
||||
|
||||
/**
|
||||
* Try to show git blame for row with error
|
||||
* @var bool
|
||||
*/
|
||||
public $blame = false;
|
||||
|
||||
/**
|
||||
* Path to git executable for blame
|
||||
* @var string
|
||||
*/
|
||||
public $gitExecutable = 'git';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $ignoreFails = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $showDeprecated = false;
|
||||
|
||||
/**
|
||||
* Path to a file with syntax error callback
|
||||
* @var string|null
|
||||
*/
|
||||
public $syntaxErrorCallbackFile = null;
|
||||
|
||||
/**
|
||||
* @param array $paths
|
||||
*/
|
||||
public function addPaths(array $paths)
|
||||
{
|
||||
$this->paths = array_merge($this->paths, $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $arguments
|
||||
* @return Settings
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function parseArguments(array $arguments)
|
||||
{
|
||||
$arguments = new ArrayIterator(array_slice($arguments, 1));
|
||||
$settings = new self;
|
||||
|
||||
// Use the currently invoked php as the default if possible
|
||||
if (defined('PHP_BINARY')) {
|
||||
$settings->phpExecutable = PHP_BINARY;
|
||||
}
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
if ($argument[0] !== '-') {
|
||||
$settings->paths[] = $argument;
|
||||
} else {
|
||||
switch ($argument) {
|
||||
case '-p':
|
||||
$settings->phpExecutable = $arguments->getNext();
|
||||
break;
|
||||
|
||||
case '-s':
|
||||
case '--short':
|
||||
$settings->shortTag = true;
|
||||
break;
|
||||
|
||||
case '-a':
|
||||
case '--asp':
|
||||
$settings->aspTags = true;
|
||||
break;
|
||||
|
||||
case '--exclude':
|
||||
$settings->excluded[] = $arguments->getNext();
|
||||
break;
|
||||
|
||||
case '-e':
|
||||
$settings->extensions = array_map('trim', explode(',', $arguments->getNext()));
|
||||
break;
|
||||
|
||||
case '-j':
|
||||
$settings->parallelJobs = max((int) $arguments->getNext(), 1);
|
||||
break;
|
||||
|
||||
case '--colors':
|
||||
$settings->colors = self::FORCED;
|
||||
break;
|
||||
|
||||
case '--no-colors':
|
||||
$settings->colors = self::DISABLED;
|
||||
break;
|
||||
|
||||
case '--no-progress':
|
||||
$settings->showProgress = false;
|
||||
break;
|
||||
|
||||
case '--json':
|
||||
$settings->format = self::FORMAT_JSON;
|
||||
break;
|
||||
|
||||
case '--gitlab':
|
||||
$settings->format = self::FORMAT_GITLAB;
|
||||
break;
|
||||
|
||||
case '--checkstyle':
|
||||
$settings->format = self::FORMAT_CHECKSTYLE;
|
||||
break;
|
||||
|
||||
case '--git':
|
||||
$settings->gitExecutable = $arguments->getNext();
|
||||
break;
|
||||
|
||||
case '--stdin':
|
||||
$settings->stdin = true;
|
||||
break;
|
||||
|
||||
case '--blame':
|
||||
$settings->blame = true;
|
||||
break;
|
||||
|
||||
case '--ignore-fails':
|
||||
$settings->ignoreFails = true;
|
||||
break;
|
||||
|
||||
case '--show-deprecated':
|
||||
$settings->showDeprecated = true;
|
||||
break;
|
||||
|
||||
case '--syntax-error-callback':
|
||||
$settings->syntaxErrorCallbackFile = $arguments->getNext();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidArgumentException($argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getPathsFromStdIn()
|
||||
{
|
||||
$content = stream_get_contents(STDIN);
|
||||
|
||||
if (empty($content)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$lines = explode("\n", rtrim($content));
|
||||
return array_map('rtrim', $lines);
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIterator extends \ArrayIterator
|
||||
{
|
||||
public function getNext()
|
||||
{
|
||||
$this->next();
|
||||
return $this->current();
|
||||
}
|
||||
}
|
||||
93
contrib/parallel-lint/src/exceptions.php
Normal file
93
contrib/parallel-lint/src/exceptions.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
namespace JakubOnderka\PhpParallelLint;
|
||||
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class Exception extends \Exception implements \JsonSerializable
|
||||
{
|
||||
#[ReturnTypeWillChange]
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array(
|
||||
'type' => get_class($this),
|
||||
'message' => $this->getMessage(),
|
||||
'code' => $this->getCode(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RunTimeException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
class InvalidArgumentException extends Exception
|
||||
{
|
||||
protected $argument;
|
||||
|
||||
public function __construct($argument)
|
||||
{
|
||||
$this->argument = $argument;
|
||||
$this->message = "Invalid argument $argument";
|
||||
}
|
||||
|
||||
public function getArgument()
|
||||
{
|
||||
return $this->argument;
|
||||
}
|
||||
}
|
||||
|
||||
class NotExistsPathException extends Exception
|
||||
{
|
||||
protected $path;
|
||||
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
$this->message = "Path '$path' not found";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
15
contrib/parallel-lint/src/polyfill.php
Normal file
15
contrib/parallel-lint/src/polyfill.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Polyfill for PHP < 5.4
|
||||
*/
|
||||
if (!interface_exists('JsonSerializable', false)) {
|
||||
interface JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @param void
|
||||
* @return mixed
|
||||
*/
|
||||
function jsonSerialize();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user