1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-14 02:07:37 +01:00
psalm/src/Psalm/IssueBuffer.php

324 lines
8.6 KiB
PHP
Raw Normal View History

<?php
2016-07-26 00:37:44 +02:00
namespace Psalm;
use Psalm\Checker\ProjectChecker;
2017-03-13 23:06:56 +01:00
use Psalm\Issue\CodeIssue;
2016-06-26 21:18:40 +02:00
class IssueBuffer
{
2016-12-08 04:38:57 +01:00
/**
* @var array<int, array>
*/
protected static $issue_data = [];
2016-11-02 07:29:00 +01:00
/**
* @var int
2016-11-02 07:29:00 +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 = [];
2017-03-13 23:06:56 +01:00
/** @var int */
protected static $recording_level = 0;
/** @var array<int, array<int, CodeIssue>> */
protected static $recorded_issues = [];
2016-12-15 01:24:16 +01:00
/**
* @var int
*/
protected static $start_time = 0;
2016-11-02 07:29:00 +01:00
/**
2017-03-13 23:06:56 +01:00
* @param CodeIssue $e
* @param array $suppressed_issues
2016-11-02 07:29:00 +01:00
* @return bool
*/
2017-03-13 23:06:56 +01:00
public static function accepts(CodeIssue $e, array $suppressed_issues = [])
{
2016-06-10 00:08:25 +02:00
$config = Config::getInstance();
$fqcn_parts = explode('\\', get_class($e));
$issue_type = array_pop($fqcn_parts);
2017-05-27 02:05:57 +02:00
if (in_array($issue_type, $suppressed_issues, true)) {
return false;
}
if (!$config->reportIssueInFile($issue_type, $e->getFilePath())) {
2016-06-10 00:08:25 +02:00
return false;
}
2017-03-13 23:06:56 +01:00
if (self::$recording_level > 0) {
self::$recorded_issues[self::$recording_level][] = $e;
2017-05-25 04:07:49 +02:00
2017-03-13 23:06:56 +01:00
return false;
}
2016-08-08 20:36:18 +02:00
return self::add($e);
}
2016-11-02 07:29:00 +01:00
/**
2017-03-13 23:06:56 +01:00
* @param CodeIssue $e
2016-11-02 07:29:00 +01:00
* @return bool
* @throws Exception\CodeException
*/
2017-03-13 23:06:56 +01:00
public static function add(CodeIssue $e)
{
$config = Config::getInstance();
2016-12-08 04:38:57 +01:00
$project_checker = ProjectChecker::getInstance();
$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();
2017-02-12 19:38:41 +01:00
$reporting_level = $config->getReportingLevelForFile($issue_type, $e->getFilePath());
2016-06-27 04:03:37 +02: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
}
}
2017-05-25 04:07:49 +02:00
return false;
}
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:
scrutinizer-ci (#152) * swapping phpcs for php-cs-fixer * workaround for php-cs-fixer treating parenthesis following echo as the function call variant * amending rules * blank_line_before_return * majority of files pass with these disabled, could remove later * combine_consecutive_unsets * concat_space * placeholder for if vimeo/psalm ever goes php:^7.0 * function_to_constant * disabling include * linebreak_after_opening_tag, lowercase_cast, magic_constant_casing * mb_str_functions disabled * method_separation * native_function_casing * native_function_invocations * new_with_braces disabled to match usage * no_alias_functions * no_blank_lines_after_class_opening * no_blank_lines_after_phpdoc * no_blank_lines_before_namespace * no_empty_comment * no_empty_phpdoc * no_empty_statement * no_extra_consecutive_blank_lines * no_leading_import_slash to discuss * no_leading_namespace_whitespace * no_mixed_echo_print * no_multiline_whitespace_around_double_arrow * no_multiline_whitespace_before_semicolons * no_php4_constructor * no_short_bool_cast * no_short_echo_tag * no_singleline_whitespace_before_semicolons * no_spaces_around_offset * no_trailing_comma_in_list_call * no_trailing_comma_in_singleline_array * no_unneeded_control_parentheses to discuss * no_unreachable_default_argument_value * no_unused_imports to discuss * no_useless_else to discuss * no_useless_return * no_whitespace_before_comma_in_array * no_whitespace_in_blank_line * non_printable_character * normalize_index_brace * ordered_class_elements to discuss * ordered_imports to discss * php_unit_construct * php_unit_dedicate_assert * php_unit_fqcn_annotation * php_unit_strict to discuss * php_unit_test_class_requires_covers to discuss * phpdoc_add_missing_param_annotation * phpdoc_align to discuss * phpdoc_annotation_without_dot to discuss * phpdoc_indent to discuss * phpdoc_inline_tag * phpdoc_no_access * phpdoc_no_alias_tag * phpdoc_no_empty_return * phpdoc_no_package * phpdoc_no_useless_inheritdoc * phpdoc_order to discuss * phpdoc_return_self_reference * phpdoc_scalar to discuss * phpdoc_separation to discuss * phpdoc_single_line_var_spacing * phpdoc_summary to discuss * phpdoc_to_comment to discuss * phpdoc_trim to discuss * phpdoc_types * phpdoc_var_without_name * pow_to_exponentiation * pre_increment to discuss * protected_to_private * psr0 turned off * psr4 turned on * random_api_migration * return_type_declaration to discuss * self_accessor to discuss * semicolon_after_instruction * short_scalar_cast * silenced_deprecation_error turned off * simplified_null_return to discuss * single_quote * space_after_semicolon * standardize_not_equals * strict_comparison to discuss * strict_param to discuss * ternary_operator_spaces * ternary_to_null_coalescing should be set to true if vimeo/psalm ever goes php:^7.0 * trailing_comma_in_multiline_array to discuss * trim_array_spaces * unary_operator_spaces * whitespace_after_comma_in_array to discuss * multi-version scrutinizer to match travis * binary_operator_space * not the best solution, but it works to exclude the call map from php-cs-fixer * reducing verbosity of config where defaults were used * dry run php-cs-fixer as part of tests * disabling rule pending FriendsOfPHP/PHP-CS-Fixer#2739 * enabling no_unused_imports * enabling ordered_imports * ignoring user-defined .php_cs * using $TRAVIS_COMMIT_RANGE to only test modified files * enabling no_leading_import_slash * conditionally testing everything * filter output then perform exact match * restoring phpcs via partial cherry pick of f65c618
2017-05-27 00:26:14 +02:00
$error_maybe_with_color = ($project_checker->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR');
echo $error_maybe_with_color . ': ' . $error_message . PHP_EOL;
2016-12-08 04:38:57 +01:00
echo self::getSnippet($e, $project_checker->use_color) . PHP_EOL . PHP_EOL;
2017-05-27 02:05:57 +02:00
++self::$error_count;
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
if ($config->stop_on_first_error) {
exit(1);
}
2016-08-08 20:36:18 +02:00
return true;
}
2016-12-08 04:38:57 +01:00
/**
2017-03-13 23:06:56 +01:00
* @param CodeIssue $e
2016-12-08 04:38:57 +01:00
* @param string $severity
* @return array
*/
2017-03-13 23:06:56 +01:00
protected static function getIssueArray(CodeIssue $e, $severity = Config::REPORT_ERROR)
2016-12-08 04:38:57 +01:00
{
$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
/**
2017-03-13 23:06:56 +01:00
* @param CodeIssue $e
2017-01-16 04:39:26 +01:00
* @param string $severity
* @return string
*/
2017-03-13 23:06:56 +01:00
protected static function getEmacsOutput(CodeIssue $e, $severity = Config::REPORT_ERROR)
2017-01-16 04:39:26 +01:00
{
$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;
}
/**
2017-03-13 23:06:56 +01:00
* @param CodeIssue $e
2016-12-08 04:38:57 +01:00
* @param boolean $use_color
* @return string
*/
2017-03-13 23:06:56 +01:00
protected static function getSnippet(CodeIssue $e, $use_color = true)
2016-12-08 04:38:57 +01:00
{
$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];
$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
/**
* @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
*/
public static function finish($is_full, $start_time, array $visited_files)
{
Provider\FileReferenceProvider::updateReferenceCache($visited_files);
if ($start_time) {
2017-05-27 02:05:57 +02:00
echo 'Checks took ' . ((float)microtime(true) - self::$start_time);
echo ' and used ' . number_format(memory_get_peak_usage() / (1024 * 1024), 3) . 'MB' . PHP_EOL;
2016-12-15 01:24:16 +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;
}
exit(1);
}
2016-10-07 06:58:08 +02:00
if ($is_full && $start_time) {
Provider\CacheProvider::processSuccessfulRun($start_time);
2016-10-07 06:58:08 +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-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;
}
/**
* @return void
*/
public static function clearCache()
{
self::$issue_data = [];
self::$emitted = [];
self::$error_count = 0;
2017-03-13 23:06:56 +01:00
self::$recording_level = 0;
self::$recorded_issues = [];
}
/**
* @return void
*/
public static function startRecording()
{
2017-05-27 02:05:57 +02:00
++self::$recording_level;
2017-03-13 23:06:56 +01:00
self::$recorded_issues[self::$recording_level] = [];
}
/**
* @return void
*/
public static function stopRecording()
{
if (self::$recording_level === 0) {
throw new \UnexpectedValueException('Cannot stop recording - already at base level');
}
2017-05-27 02:05:57 +02:00
--self::$recording_level;
2017-03-13 23:06:56 +01:00
}
/**
* @return array<int, CodeIssue>
*/
public static function clearRecordingLevel()
{
if (self::$recording_level === 0) {
throw new \UnexpectedValueException('Not currently recording');
}
$recorded_issues = self::$recorded_issues[self::$recording_level];
self::$recorded_issues[self::$recording_level] = [];
return $recorded_issues;
}
/**
* @return void
*/
public static function bubbleUp(CodeIssue $e)
{
if (self::$recording_level === 0) {
self::add($e);
2017-05-25 04:07:49 +02:00
2017-03-13 23:06:56 +01:00
return;
}
self::$recorded_issues[self::$recording_level][] = $e;
}
}