2016-06-06 02:25:16 +02:00
|
|
|
<?php
|
2016-07-26 00:37:44 +02:00
|
|
|
namespace Psalm;
|
2016-06-06 02:25:16 +02:00
|
|
|
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\Checker\ProjectChecker;
|
|
|
|
|
2016-06-26 21:18:40 +02:00
|
|
|
class IssueBuffer
|
2016-06-06 02:25:16 +02:00
|
|
|
{
|
2016-12-08 04:38:57 +01:00
|
|
|
/**
|
|
|
|
* @var array<int, array>
|
|
|
|
*/
|
|
|
|
protected static $issue_data = [];
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2017-01-14 07:24:27 +01:00
|
|
|
* @var int
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2017-01-14 07:24:27 +01:00
|
|
|
protected static $error_count = 0;
|
2016-11-01 05:39:41 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2016-10-19 00:55:53 +02:00
|
|
|
protected static $emitted = [];
|
2016-06-21 01:30:38 +02:00
|
|
|
|
2016-12-15 01:24:16 +01:00
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected static $start_time = 0;
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param Issue\CodeIssue $e
|
|
|
|
* @param array $suppressed_issues
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-07-22 19:29:46 +02:00
|
|
|
public static function accepts(Issue\CodeIssue $e, array $suppressed_issues = [])
|
2016-06-06 02:25:16 +02:00
|
|
|
{
|
2016-06-10 00:08:25 +02:00
|
|
|
$config = Config::getInstance();
|
|
|
|
|
2016-07-26 21:00:40 +02:00
|
|
|
$fqcn_parts = explode('\\', get_class($e));
|
|
|
|
$issue_type = array_pop($fqcn_parts);
|
2016-07-22 19:29:46 +02:00
|
|
|
|
|
|
|
if (in_array($issue_type, $suppressed_issues)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-01-16 18:59:09 +01:00
|
|
|
if ($config->excludeIssueInFile($issue_type, $e->getFilePath())) {
|
2016-06-10 00:08:25 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-08-08 20:36:18 +02:00
|
|
|
return self::add($e);
|
2016-06-27 19:22:16 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
|
|
|
* @param Issue\CodeIssue $e
|
|
|
|
* @return bool
|
|
|
|
* @throws Exception\CodeException
|
|
|
|
*/
|
2016-06-27 19:22:16 +02:00
|
|
|
public static function add(Issue\CodeIssue $e)
|
|
|
|
{
|
|
|
|
$config = Config::getInstance();
|
2016-12-08 04:38:57 +01:00
|
|
|
$project_checker = ProjectChecker::getInstance();
|
2016-06-27 19:22:16 +02:00
|
|
|
|
2016-07-26 21:00:40 +02:00
|
|
|
$fqcn_parts = explode('\\', get_class($e));
|
|
|
|
$issue_type = array_pop($fqcn_parts);
|
2016-06-17 23:34:52 +02:00
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
$error_message = $issue_type . ' - ' . $e->getShortLocation() . ' - ' . $e->getMessage();
|
2016-07-22 19:29:46 +02:00
|
|
|
|
2017-02-12 19:38:41 +01:00
|
|
|
$reporting_level = $config->getReportingLevelForFile($issue_type, $e->getFilePath());
|
2016-06-27 04:03:37 +02:00
|
|
|
|
2016-12-04 01:11:30 +01:00
|
|
|
if ($reporting_level === Config::REPORT_SUPPRESS) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($reporting_level === Config::REPORT_INFO) {
|
2016-12-08 04:38:57 +01:00
|
|
|
if ($project_checker->show_info && !self::alreadyEmitted($error_message)) {
|
|
|
|
switch ($project_checker->output_format) {
|
|
|
|
case ProjectChecker::TYPE_CONSOLE:
|
|
|
|
echo 'INFO: ' . $error_message . PHP_EOL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ProjectChecker::TYPE_JSON:
|
|
|
|
self::$issue_data[] = self::getIssueArray($e, Config::REPORT_INFO);
|
|
|
|
break;
|
2017-01-16 04:39:26 +01:00
|
|
|
|
|
|
|
case ProjectChecker::TYPE_EMACS:
|
|
|
|
echo self::getEmacsOutput($e, Config::REPORT_INFO) . PHP_EOL;
|
|
|
|
break;
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
2016-12-04 01:11:30 +01:00
|
|
|
}
|
|
|
|
return false;
|
2016-06-10 20:47:44 +02:00
|
|
|
}
|
|
|
|
|
2016-06-27 04:40:57 +02:00
|
|
|
if ($config->throw_exception) {
|
|
|
|
throw new Exception\CodeException($error_message);
|
|
|
|
}
|
|
|
|
|
2016-10-19 00:55:53 +02:00
|
|
|
if (!self::alreadyEmitted($error_message)) {
|
2016-12-08 04:38:57 +01:00
|
|
|
switch ($project_checker->output_format) {
|
|
|
|
case ProjectChecker::TYPE_CONSOLE:
|
|
|
|
echo ($project_checker->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR') .
|
|
|
|
': ' . $error_message . PHP_EOL;
|
|
|
|
|
|
|
|
echo self::getSnippet($e, $project_checker->use_color) . PHP_EOL . PHP_EOL;
|
2017-01-14 07:24:27 +01:00
|
|
|
self::$error_count++;
|
2016-12-04 01:11:30 +01:00
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ProjectChecker::TYPE_JSON:
|
|
|
|
self::$issue_data[] = self::getIssueArray($e);
|
|
|
|
break;
|
2017-01-16 04:39:26 +01:00
|
|
|
|
|
|
|
case ProjectChecker::TYPE_EMACS:
|
|
|
|
echo self::getEmacsOutput($e) . PHP_EOL;
|
|
|
|
break;
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
2016-10-19 00:55:53 +02:00
|
|
|
}
|
2016-06-17 01:02:29 +02:00
|
|
|
|
2016-06-21 01:30:38 +02:00
|
|
|
if ($config->stop_on_first_error) {
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2016-08-08 20:36:18 +02:00
|
|
|
return true;
|
2016-06-06 02:25:16 +02:00
|
|
|
}
|
2016-06-21 01:30:38 +02:00
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
/**
|
|
|
|
* @param Issue\CodeIssue $e
|
|
|
|
* @param string $severity
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected static function getIssueArray(Issue\CodeIssue $e, $severity = Config::REPORT_ERROR)
|
|
|
|
{
|
|
|
|
$location = $e->getLocation();
|
|
|
|
$selection_bounds = $location->getSelectionBounds();
|
|
|
|
|
|
|
|
return [
|
|
|
|
'type' => $severity,
|
|
|
|
'line_number' => $location->getLineNumber(),
|
|
|
|
'message' => $e->getMessage(),
|
|
|
|
'file_name' => $location->file_name,
|
|
|
|
'file_path' => $location->file_path,
|
|
|
|
'snippet' => $location->getSnippet(),
|
|
|
|
'from' => $selection_bounds[0],
|
|
|
|
'to' => $selection_bounds[1],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2017-01-16 04:39:26 +01:00
|
|
|
/**
|
|
|
|
* @param Issue\CodeIssue $e
|
|
|
|
* @param string $severity
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected static function getEmacsOutput(Issue\CodeIssue $e, $severity = Config::REPORT_ERROR)
|
|
|
|
{
|
|
|
|
$location = $e->getLocation();
|
|
|
|
|
|
|
|
return $location->file_path . ':' . $location->getLineNumber() . ':' . $location->getColumn() . ': ' .
|
|
|
|
($severity === Config::REPORT_ERROR ? 'error' : 'warning') . ' - ' . $e->getMessage();
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
/**
|
|
|
|
* @return array<int, array>
|
|
|
|
*/
|
|
|
|
public static function getIssueData()
|
|
|
|
{
|
|
|
|
return self::$issue_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Issue\CodeIssue $e
|
|
|
|
* @param boolean $use_color
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected static function getSnippet(Issue\CodeIssue $e, $use_color = true)
|
|
|
|
{
|
|
|
|
$location = $e->getLocation();
|
|
|
|
|
|
|
|
$snippet = $location->getSnippet();
|
|
|
|
|
|
|
|
if (!$use_color) {
|
|
|
|
return $snippet;
|
|
|
|
}
|
|
|
|
|
|
|
|
$snippet_bounds = $location->getSnippetBounds();
|
|
|
|
$selection_bounds = $location->getSelectionBounds();
|
|
|
|
|
|
|
|
$selection_start = $selection_bounds[0] - $snippet_bounds[0];
|
2016-12-08 21:57:18 +01:00
|
|
|
$selection_length = $selection_bounds[1] - $selection_bounds[0];
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
return substr($snippet, 0, $selection_start) .
|
|
|
|
"\e[97;41m" . substr($snippet, $selection_start, $selection_length) .
|
|
|
|
"\e[0m" . substr($snippet, $selection_length + $selection_start) . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2017-02-18 19:41:27 +01:00
|
|
|
* @param bool $is_full
|
|
|
|
* @param int|null $start_time
|
|
|
|
* @param array<string, bool> $visited_files
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-02-18 19:41:27 +01:00
|
|
|
public static function finish($is_full, $start_time, array $visited_files)
|
2016-06-21 01:30:38 +02:00
|
|
|
{
|
2017-02-18 19:41:27 +01:00
|
|
|
Provider\FileReferenceProvider::updateReferenceCache($visited_files);
|
2016-10-05 19:24:46 +02:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($start_time) {
|
2016-12-24 12:03:55 +01:00
|
|
|
echo('Checks took ' . ((float)microtime(true) - self::$start_time));
|
2017-01-06 07:07:11 +01:00
|
|
|
echo(' and used ' . number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'MB' . PHP_EOL);
|
2016-12-15 01:24:16 +01:00
|
|
|
}
|
|
|
|
|
2017-01-14 07:24:27 +01:00
|
|
|
if (self::$error_count) {
|
2016-12-08 04:38:57 +01:00
|
|
|
$project_checker = ProjectChecker::getInstance();
|
|
|
|
if ($project_checker->output_format === ProjectChecker::TYPE_JSON) {
|
|
|
|
echo json_encode(self::$issue_data) . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
2016-06-21 01:30:38 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
2016-10-07 06:58:08 +02:00
|
|
|
|
2016-11-06 05:59:29 +01:00
|
|
|
if ($is_full && $start_time) {
|
2017-02-18 19:41:27 +01:00
|
|
|
Provider\CacheProvider::processSuccessfulRun($start_time);
|
2016-10-07 06:58:08 +02:00
|
|
|
}
|
2016-06-21 01:30:38 +02:00
|
|
|
}
|
2016-10-19 00:55:53 +02:00
|
|
|
|
2016-10-30 17:46:18 +01:00
|
|
|
/**
|
|
|
|
* @param string $message
|
|
|
|
* @return bool
|
|
|
|
*/
|
2016-10-19 00:55:53 +02:00
|
|
|
protected static function alreadyEmitted($message)
|
|
|
|
{
|
|
|
|
$sham = sha1($message);
|
|
|
|
|
|
|
|
if (isset(self::$emitted[$sham])) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
self::$emitted[$sham] = true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2016-12-08 21:57:18 +01:00
|
|
|
|
2016-12-15 01:24:16 +01:00
|
|
|
/**
|
2016-12-17 06:48:31 +01:00
|
|
|
* @param int $time
|
2016-12-15 01:24:16 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function setStartTime($time)
|
|
|
|
{
|
|
|
|
self::$start_time = $time;
|
|
|
|
}
|
|
|
|
|
2016-12-08 21:57:18 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public static function clearCache()
|
|
|
|
{
|
|
|
|
self::$issue_data = [];
|
|
|
|
self::$emitted = [];
|
2017-01-14 07:24:27 +01:00
|
|
|
self::$error_count = 0;
|
2016-12-08 21:57:18 +01:00
|
|
|
}
|
2016-06-06 02:25:16 +02:00
|
|
|
}
|