1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Replace issue data array with object

This commit is contained in:
Matthew Brown 2020-02-16 18:24:40 -05:00
parent d3bfb96431
commit 196a0a5c4e
28 changed files with 596 additions and 447 deletions

View File

@ -15,6 +15,7 @@ use function min;
use const PHP_VERSION;
use function phpversion;
use function preg_replace_callback;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Provider\FileProvider;
use RuntimeException;
use function str_replace;
@ -53,12 +54,7 @@ class ErrorBaseline
/**
* @param FileProvider $fileProvider
* @param string $baselineFile
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
* @param array<string, list<IssueData>> $issues
*
* @return void
*/
@ -136,12 +132,7 @@ class ErrorBaseline
/**
* @param FileProvider $fileProvider
* @param string $baselineFile
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
* @param array<string, list<IssueData>> $issues
*
* @throws Exception\ConfigException
*
@ -189,12 +180,7 @@ class ErrorBaseline
}
/**
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
* @param array<string, list<IssueData>> $issues
*
* @return array<string,array<string,array{o:int, s:array<int, string>}>>
*/
@ -207,18 +193,17 @@ class ErrorBaseline
array_merge(...array_values($issues)),
/**
* @param array<string,array<string,array{o:int, s:array<int, string>}>> $carry
* @param array{type: string, file_name: string, severity: string, selected_text: string} $issue
*
* @return array<string,array<string,array{o:int, s:array<int, string>}>>
*/
function (array $carry, array $issue): array {
if ($issue['severity'] !== Config::REPORT_ERROR) {
function (array $carry, IssueData $issue): array {
if ($issue->severity !== Config::REPORT_ERROR) {
return $carry;
}
$fileName = $issue['file_name'];
$fileName = $issue->file_name;
$fileName = str_replace('\\', '/', $fileName);
$issueType = $issue['type'];
$issueType = $issue->type;
if (!isset($carry[$fileName])) {
$carry[$fileName] = [];
@ -230,8 +215,8 @@ class ErrorBaseline
++$carry[$fileName][$issueType]['o'];
if (!strpos($issue['selected_text'], "\n")) {
$carry[$fileName][$issueType]['s'][] = $issue['selected_text'];
if (!strpos($issue->selected_text, "\n")) {
$carry[$fileName][$issueType]['s'][] = $issue->selected_text;
}
return $carry;

View File

@ -0,0 +1,146 @@
<?php
namespace Psalm\Internal\Analyzer;
class IssueData
{
/**
* @var string
*/
public $severity;
/**
* @var int
* @readonly
*/
public $line_from;
/**
* @var int
* @readonly
*/
public $line_to;
/**
* @var string
* @readonly
*/
public $type;
/**
* @var string
* @readonly
*/
public $message;
/**
* @var string
* @readonly
*/
public $file_name;
/**
* @var string
* @readonly
*/
public $file_path;
/**
* @var string
* @readonly
*/
public $snippet;
/**
* @var string
* @readonly
*/
public $selected_text;
/**
* @var int
* @readonly
*/
public $from;
/**
* @var int
* @readonly
*/
public $to;
/**
* @var int
* @readonly
*/
public $snippet_from;
/**
* @var int
* @readonly
*/
public $snippet_to;
/**
* @var int
* @readonly
*/
public $column_from;
/**
* @var int
* @readonly
*/
public $column_to;
/**
* @param string $severity
* @param int $line_from
* @param int $line_to
* @param string $type
* @param string $message
* @param string $file_name
* @param string $file_path
* @param string $snippet
* @param string $selected_text
* @param int $from
* @param int $to
* @param int $snippet_from
* @param int $snippet_to
* @param int $column_from
* @param int $column_to
*/
public function __construct(
$severity,
$line_from,
$line_to,
$type,
$message,
$file_name,
$file_path,
$snippet,
$selected_text,
$from,
$to,
$snippet_from,
$snippet_to,
$column_from,
$column_to
) {
$this->severity = $severity;
$this->line_from = $line_from;
$this->line_to = $line_to;
$this->type = $type;
$this->message = $message;
$this->file_name = $file_name;
$this->file_path = $file_path;
$this->snippet = $snippet;
$this->selected_text = $selected_text;
$this->from = $from;
$this->to = $to;
$this->snippet_from = $snippet_from;
$this->snippet_to = $snippet_to;
$this->column_from = $column_from;
$this->column_to = $column_to;
}
}

View File

@ -12,6 +12,7 @@ use PhpParser;
use function preg_replace;
use Psalm\Config;
use Psalm\FileManipulation;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\FileAnalyzer;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
@ -25,24 +26,6 @@ use function substr;
use function usort;
/**
* @psalm-type IssueData = array{
* severity: string,
* line_from: int,
* line_to: int,
* type: string,
* message: string,
* file_name: string,
* file_path: string,
* snippet: string,
* from: int,
* to: int,
* snippet_from: int,
* snippet_to: int,
* column_from: int,
* column_to: int,
* selected_text: string
* }
*
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*
* @psalm-type WorkerData = array{
@ -349,12 +332,12 @@ class Analyzer
$has_info = false;
foreach ($issues as $issue) {
if ($issue['severity'] === 'error') {
if ($issue->severity === 'error') {
$has_error = true;
break;
}
if ($issue['severity'] === 'info') {
if ($issue->severity === 'info') {
$has_info = true;
}
}
@ -865,7 +848,7 @@ class Analyzer
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
foreach ($file_issues as $i => &$issue_data) {
if ($issue_data['to'] < $first_diff_offset || $issue_data['from'] > $last_diff_offset) {
if ($issue_data->to < $first_diff_offset || $issue_data->from > $last_diff_offset) {
unset($file_issues[$i]);
continue;
}
@ -873,16 +856,16 @@ class Analyzer
$matched = false;
foreach ($file_diff_map as list($from, $to, $file_offset, $line_offset)) {
if ($issue_data['from'] >= $from
&& $issue_data['from'] <= $to
if ($issue_data->from >= $from
&& $issue_data->from <= $to
&& !$matched
) {
$issue_data['from'] += $file_offset;
$issue_data['to'] += $file_offset;
$issue_data['snippet_from'] += $file_offset;
$issue_data['snippet_to'] += $file_offset;
$issue_data['line_from'] += $line_offset;
$issue_data['line_to'] += $line_offset;
$issue_data->from += $file_offset;
$issue_data->to += $file_offset;
$issue_data->snippet_from += $file_offset;
$issue_data->snippet_to += $file_offset;
$issue_data->line_from += $line_offset;
$issue_data->line_to += $line_offset;
$matched = true;
}
}
@ -1351,8 +1334,8 @@ class Analyzer
$applicable_issues = [];
foreach ($this->existing_issues[$file_path] as $issue_data) {
if ($issue_data['from'] >= $start && $issue_data['from'] <= $end) {
if ($issue_type === null || $issue_type === $issue_data['type']) {
if ($issue_data->from >= $start && $issue_data->from <= $end) {
if ($issue_type === null || $issue_type === $issue_data->type) {
$applicable_issues[] = $issue_data;
}
}
@ -1372,8 +1355,8 @@ class Analyzer
{
if (isset($this->existing_issues[$file_path])) {
foreach ($this->existing_issues[$file_path] as $i => $issue_data) {
if ($issue_data['from'] >= $start && $issue_data['from'] <= $end) {
if ($issue_type === null || $issue_type === $issue_data['type']) {
if ($issue_data->from >= $start && $issue_data->from <= $end) {
if ($issue_type === null || $issue_type === $issue_data->type) {
unset($this->existing_issues[$file_path][$i]);
}
}

View File

@ -14,6 +14,7 @@ use function min;
use const PHP_EOL;
use Psalm\Codebase;
use Psalm\Config;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Provider\FileProvider;
use Psalm\Internal\Provider\FileReferenceProvider;
use Psalm\Internal\Provider\FileStorageProvider;
@ -24,24 +25,6 @@ use function strtolower;
use function substr;
/**
* @psalm-type IssueData = array{
* severity: string,
* line_from: int,
* line_to: int,
* type: string,
* message: string,
* file_name: string,
* file_path: string,
* snippet: string,
* from: int,
* to: int,
* snippet_from: int,
* snippet_to: int,
* column_from: int,
* column_to: int,
* selected_text: string
* }
*
* @psalm-type PoolData = array{
* classlikes_data:array{
* 0:array<lowercase-string, bool>,

View File

@ -29,6 +29,7 @@ use LanguageServerProtocol\TextDocumentSyncKind;
use LanguageServerProtocol\TextDocumentSyncOptions;
use function max;
use function parse_url;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\LanguageServer\Server\TextDocument;
use function rawurlencode;
@ -326,25 +327,15 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
foreach ($uris as $file_path => $uri) {
$diagnostics = array_map(
/**
* @param array{
* severity: string,
* message: string,
* line_from: int,
* line_to: int,
* column_from: int,
* column_to: int
* } $issue_data
*/
function (array $issue_data) use ($file_path) : Diagnostic {
//$check_name = $issue['check_name'];
$description = $issue_data['message'];
$severity = $issue_data['severity'];
function (IssueData $issue_data) use ($file_path) : Diagnostic {
//$check_name = $issue->check_name;
$description = $issue_data->message;
$severity = $issue_data->severity;
$start_line = max($issue_data['line_from'], 1);
$end_line = $issue_data['line_to'];
$start_column = $issue_data['column_from'];
$end_column = $issue_data['column_to'];
$start_line = max($issue_data->line_from, 1);
$end_line = $issue_data->line_to;
$start_column = $issue_data->column_from;
$end_column = $issue_data->column_to;
// Language server has 0 based lines and columns, phan has 1-based lines and columns.
$range = new Range(
new Position($start_line - 1, $start_column - 1),

View File

@ -7,28 +7,12 @@ use function file_get_contents;
use function file_put_contents;
use function is_array;
use function is_readable;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Config;
use function serialize;
use function unserialize;
/**
* @psalm-type IssueData = array{
* severity: string,
* line_from: int,
* line_to: int,
* type: string,
* message: string,
* file_name: string,
* file_path: string,
* snippet: string,
* from: int,
* to: int,
* snippet_from: int,
* snippet_to: int,
* column_from: int,
* column_to: int
* }
*
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*/
/**

View File

@ -8,27 +8,10 @@ use function array_unique;
use function file_exists;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
/**
* @psalm-type IssueData = array{
* severity: string,
* line_from: int,
* line_to: int,
* type: string,
* message: string,
* file_name: string,
* file_path: string,
* snippet: string,
* from: int,
* to: int,
* snippet_from: int,
* snippet_to: int,
* column_from: int,
* column_to: int,
* selected_text: string
* }
*
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*/
/**
@ -924,10 +907,10 @@ class FileReferenceProvider
*
* @return void
*/
public function addIssue($file_path, array $issue)
public function addIssue($file_path, IssueData $issue)
{
// dont save parse errors ever, as they're not responsive to AST diffing
if ($issue['type'] === 'ParseError') {
if ($issue->type === 'ParseError') {
return;
}

View File

@ -93,11 +93,9 @@ abstract class CodeIssue
/**
* @param string $severity
*
* @return array{severity: string, line_from: int, line_to: int, type: string, message: string, file_name: string,
* file_path: string, snippet: string, selected_text: string, from: int, to: int, snippet_from: int,
* snippet_to: int, column_from: int, column_to: int}
* @return \Psalm\Internal\Analyzer\IssueData
*/
public function toArray($severity = Config::REPORT_ERROR)
public function toIssueData($severity = Config::REPORT_ERROR)
{
$location = $this->getLocation();
$selection_bounds = $location->getSelectionBounds();
@ -106,22 +104,22 @@ abstract class CodeIssue
$fqcn_parts = explode('\\', get_called_class());
$issue_type = array_pop($fqcn_parts);
return [
'severity' => $severity,
'line_from' => $location->getLineNumber(),
'line_to' => $location->getEndLineNumber(),
'type' => $issue_type,
'message' => $this->getMessage(),
'file_name' => $location->file_name,
'file_path' => $location->file_path,
'snippet' => $location->getSnippet(),
'selected_text' => $location->getSelectedText(),
'from' => $selection_bounds[0],
'to' => $selection_bounds[1],
'snippet_from' => $snippet_bounds[0],
'snippet_to' => $snippet_bounds[1],
'column_from' => $location->getColumn(),
'column_to' => $location->getEndColumn(),
];
return new \Psalm\Internal\Analyzer\IssueData(
$severity,
$location->getLineNumber(),
$location->getEndLineNumber(),
$issue_type,
$this->getMessage(),
$location->file_name,
$location->file_path,
$location->getSnippet(),
$location->getSelectedText(),
$selection_bounds[0],
$selection_bounds[1],
$snippet_bounds[0],
$snippet_bounds[1],
$location->getColumn(),
$location->getEndColumn(),
);
}
}

View File

@ -11,6 +11,7 @@ use function get_class;
use function memory_get_peak_usage;
use function microtime;
use function number_format;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Issue\CodeIssue;
use Psalm\Issue\UnusedPsalmSuppress;
@ -36,9 +37,7 @@ use function array_values;
class IssueBuffer
{
/**
* @var array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>>
* @var array<string, list<IssueData>>
*/
protected static $issues_data = [];
@ -211,7 +210,7 @@ class IssueBuffer
if ($reporting_level === Config::REPORT_INFO) {
if (!self::alreadyEmitted($emitted_key)) {
self::$issues_data[$e->getFilePath()][] = $e->toArray(Config::REPORT_INFO);
self::$issues_data[$e->getFilePath()][] = $e->toIssueData(Config::REPORT_INFO);
}
return false;
@ -230,7 +229,7 @@ class IssueBuffer
if (!self::alreadyEmitted($emitted_key)) {
++self::$error_count;
self::$issues_data[$e->getFilePath()][] = $e->toArray(Config::REPORT_ERROR);
self::$issues_data[$e->getFilePath()][] = $e->toIssueData(Config::REPORT_ERROR);
}
if ($is_fixable) {
@ -249,7 +248,7 @@ class IssueBuffer
$filtered_issues = [];
foreach (self::$issues_data[$file_path] as $issue) {
if ($issue['type'] !== $issue_type || $issue['from'] !== $file_offset) {
if ($issue->type !== $issue_type || $issue->from !== $file_offset) {
$filtered_issues[] = $issue;
}
}
@ -267,9 +266,7 @@ class IssueBuffer
}
/**
* @return array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int, snippet_from: int, snippet_to: int,
* column_from: int, column_to: int, selected_text: string}>>
* @return array<string, list<IssueData>>
*/
public static function getIssuesData()
{
@ -277,9 +274,7 @@ class IssueBuffer
}
/**
* @return list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int, snippet_from: int, snippet_to: int,
* column_from: int, column_to: int, selected_text: string}>
* @return list<IssueData>
*/
public static function getIssuesDataForFile(string $file_path)
{
@ -387,9 +382,7 @@ class IssueBuffer
}
/**
* @param array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int, snippet_from: int,
* snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues_data
* @param array<string, list<IssueData>> $issues_data
*
* @return void
*/
@ -397,10 +390,10 @@ class IssueBuffer
{
foreach ($issues_data as $file_path => $file_issues) {
foreach ($file_issues as $issue) {
$emitted_key = $issue['type']
. '-' . $issue['file_name']
. ':' . $issue['line_from']
. ':' . $issue['column_from'];
$emitted_key = $issue->type
. '-' . $issue->file_name
. ':' . $issue->line_from
. ':' . $issue->column_from;
if (!self::alreadyEmitted($emitted_key)) {
self::$issues_data[$file_path][] = $issue;
@ -444,24 +437,20 @@ class IssueBuffer
foreach (self::$issues_data as $file_path => $file_issues) {
usort(
$file_issues,
/**
* @param array{file_path: string, line_from: int, column_from: int} $d1
* @param array{file_path: string, line_from: int, column_from: int} $d2
*/
function (array $d1, array $d2) : int {
if ($d1['file_path'] === $d2['file_path']) {
if ($d1['line_from'] === $d2['line_from']) {
if ($d1['column_from'] === $d2['column_from']) {
function (IssueData $d1, IssueData $d2) : int {
if ($d1->file_path === $d2->file_path) {
if ($d1->line_from === $d2->line_from) {
if ($d1->column_from === $d2->column_from) {
return 0;
}
return $d1['column_from'] > $d2['column_from'] ? 1 : -1;
return $d1->column_from > $d2->column_from ? 1 : -1;
}
return $d1['line_from'] > $d2['line_from'] ? 1 : -1;
return $d1->line_from > $d2->line_from ? 1 : -1;
}
return $d1['file_path'] > $d2['file_path'] ? 1 : -1;
return $d1->file_path > $d2->file_path ? 1 : -1;
}
);
self::$issues_data[$file_path] = $file_issues;
@ -471,26 +460,26 @@ class IssueBuffer
// Set severity for issues in baseline to INFO
foreach (self::$issues_data as $file_path => $file_issues) {
foreach ($file_issues as $key => $issue_data) {
$file = $issue_data['file_name'];
$file = $issue_data->file_name;
$file = str_replace('\\', '/', $file);
$type = $issue_data['type'];
$type = $issue_data->type;
if (isset($issue_baseline[$file][$type]) && $issue_baseline[$file][$type]['o'] > 0) {
if ($issue_baseline[$file][$type]['o'] === count($issue_baseline[$file][$type]['s'])) {
$position = array_search(
$issue_data['selected_text'],
$issue_data->selected_text,
$issue_baseline[$file][$type]['s'],
true
);
if ($position !== false) {
$issue_data['severity'] = Config::REPORT_INFO;
$issue_data->severity = Config::REPORT_INFO;
array_splice($issue_baseline[$file][$type]['s'], $position, 1);
$issue_baseline[$file][$type]['o'] = $issue_baseline[$file][$type]['o'] - 1;
}
} else {
$issue_baseline[$file][$type]['s'] = [];
$issue_data['severity'] = Config::REPORT_INFO;
$issue_data->severity = Config::REPORT_INFO;
$issue_baseline[$file][$type]['o'] = $issue_baseline[$file][$type]['o'] - 1;
}
}
@ -509,7 +498,7 @@ class IssueBuffer
foreach (self::$issues_data as $file_issues) {
foreach ($file_issues as $issue_data) {
if ($issue_data['severity'] === Config::REPORT_ERROR) {
if ($issue_data->severity === Config::REPORT_ERROR) {
++$error_count;
} else {
++$info_count;
@ -735,9 +724,7 @@ class IssueBuffer
}
/**
* @return array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int, snippet_from: int, snippet_to: int,
* column_from: int, column_to: int}>>
* @return array<string, list<IssueData>>
*/
public static function clear()
{

View File

@ -1,6 +1,7 @@
<?php
namespace Psalm\Plugin\Hook;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Codebase;
use Psalm\SourceControl\SourceControlInfo;
@ -9,9 +10,7 @@ interface AfterAnalysisInterface
/**
* Called after analysis is complete
*
* @param array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
* @param array<string, list<IssueData>> $issues
*
* @return void
*/

View File

@ -19,6 +19,7 @@ use function parse_url;
use const PHP_EOL;
use const PHP_URL_SCHEME;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\SourceControl\SourceControlInfo;
use const STDERR;
use function strlen;
@ -32,9 +33,7 @@ class Shepherd implements \Psalm\Plugin\Hook\AfterAnalysisInterface
/**
* Called after analysis is complete
*
* @param array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
* @param array<string, list<IssueData>> $issues
*
* @return void
*/
@ -61,11 +60,8 @@ class Shepherd implements \Psalm\Plugin\Hook\AfterAnalysisInterface
if ($build_info) {
$normalized_data = $issues === [] ? [] : array_filter(
array_merge(...array_values($issues)),
/**
* @param array{severity: string} $i
*/
static function (array $i) : bool {
return $i['severity'] === 'error';
static function (IssueData $i) : bool {
return $i->severity === 'error';
}
);

View File

@ -2,6 +2,7 @@
namespace Psalm;
use function array_filter;
use Psalm\Internal\Analyzer\IssueData;
abstract class Report
{
@ -34,9 +35,7 @@ abstract class Report
];
/**
* @var array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>
* @var array<int, IssueData>
*/
protected $issues_data;
@ -59,9 +58,7 @@ abstract class Report
protected $total_expression_count;
/**
* @param array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}> $issues_data
* @param array<int, IssueData> $issues_data
* @param array<string, int> $fixable_issue_counts
* @param bool $use_color
* @param bool $show_snippet
@ -77,11 +74,8 @@ abstract class Report
if (!$report_options->show_info) {
$this->issues_data = array_filter(
$issues_data,
/**
* @param array{severity: string} $issue_data
*/
function (array $issue_data) : bool {
return $issue_data['severity'] !== Config::REPORT_INFO;
function ($issue_data) : bool {
return $issue_data->severity !== Config::REPORT_INFO;
}
);
} else {

View File

@ -19,16 +19,16 @@ class CheckstyleReport extends Report
foreach ($this->issues_data as $issue_data) {
$message = sprintf(
'%s: %s',
$issue_data['type'],
$issue_data['message']
$issue_data->type,
$issue_data->message
);
$output .= '<file name="' . htmlspecialchars($issue_data['file_name']) . '">' . "\n";
$output .= '<file name="' . htmlspecialchars($issue_data->file_name) . '">' . "\n";
$output .= ' ';
$output .= '<error';
$output .= ' line="' . $issue_data['line_from'] . '"';
$output .= ' column="' . $issue_data['column_from'] . '"';
$output .= ' severity="' . $issue_data['severity'] . '"';
$output .= ' line="' . $issue_data->line_from . '"';
$output .= ' column="' . $issue_data->column_from . '"';
$output .= ' severity="' . $issue_data->severity . '"';
$output .= ' message="' . htmlspecialchars($message) . '"';
$output .= '/>' . "\n";
$output .= '</file>' . "\n";

View File

@ -32,44 +32,44 @@ class CompactReport extends Report
$output = [];
foreach ($this->issues_data as $i => $issue_data) {
if (!$this->show_info && $issue_data['severity'] === Config::REPORT_INFO) {
if (!$this->show_info && $issue_data->severity === Config::REPORT_INFO) {
continue;
} elseif (is_null($current_file) || $current_file !== $issue_data['file_name']) {
} elseif (is_null($current_file) || $current_file !== $issue_data->file_name) {
// If we're processing a new file, then wrap up the last table and render it out.
if ($buffer !== null) {
$table->render();
$output[] = $buffer->fetch();
}
$output[] = 'FILE: ' . $issue_data['file_name'] . "\n";
$output[] = 'FILE: ' . $issue_data->file_name . "\n";
$buffer = new BufferedOutput();
$table = new Table($buffer);
$table->setHeaders(['SEVERITY', 'LINE', 'ISSUE', 'DESCRIPTION']);
}
$is_error = $issue_data['severity'] === Config::REPORT_ERROR;
$is_error = $issue_data->severity === Config::REPORT_ERROR;
if ($is_error) {
$severity = ($this->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR');
} else {
$severity = strtoupper($issue_data['severity']);
$severity = strtoupper($issue_data->severity);
}
// Since `Table::setColumnMaxWidth` is only available in symfony/console 4.2+ we need do something similar
// so we have clean tables.
$message = $issue_data['message'];
$message = $issue_data->message;
if (strlen($message) > 70) {
$message = implode("\n", str_split($message, 70));
}
$table->addRow([
$severity,
$issue_data['line_from'],
$issue_data['type'],
$issue_data->line_from,
$issue_data->type,
$message,
]);
$current_file = $issue_data['file_name'];
$current_file = $issue_data->file_name;
// If we're at the end of the issue sets, then wrap up the last table and render it out.
if ($i === count($this->issues_data) - 1) {

View File

@ -20,18 +20,11 @@ class ConsoleReport extends Report
return $output;
}
/**
* @param array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int} $issue_data
*
* @return string
*/
private function format(array $issue_data): string
private function format(\Psalm\Internal\Analyzer\IssueData $issue_data): string
{
$issue_string = '';
$is_error = $issue_data['severity'] === Config::REPORT_ERROR;
$is_error = $issue_data->severity === Config::REPORT_ERROR;
if ($is_error) {
$issue_string .= ($this->use_color ? "\e[0;31mERROR\e[0m" : 'ERROR');
@ -39,17 +32,17 @@ class ConsoleReport extends Report
$issue_string .= 'INFO';
}
$issue_string .= ': ' . $issue_data['type'] . ' - ' . $issue_data['file_name'] . ':' .
$issue_data['line_from'] . ':' . $issue_data['column_from'] . ' - ' . $issue_data['message'] . "\n";
$issue_string .= ': ' . $issue_data->type . ' - ' . $issue_data->file_name . ':' .
$issue_data->line_from . ':' . $issue_data->column_from . ' - ' . $issue_data->message . "\n";
if ($this->show_snippet) {
$snippet = $issue_data['snippet'];
$snippet = $issue_data->snippet;
if (!$this->use_color) {
$issue_string .= $snippet;
} else {
$selection_start = $issue_data['from'] - $issue_data['snippet_from'];
$selection_length = $issue_data['to'] - $issue_data['from'];
$selection_start = $issue_data->from - $issue_data->snippet_from;
$selection_length = $issue_data->to - $issue_data->from;
$issue_string .= substr($snippet, 0, $selection_start)
. ($is_error ? "\e[97;41m" : "\e[30;47m") . substr($snippet, $selection_start, $selection_length)

View File

@ -16,11 +16,11 @@ class EmacsReport extends Report
foreach ($this->issues_data as $issue_data) {
$output .= sprintf(
'%s:%s:%s:%s - %s',
$issue_data['file_path'],
$issue_data['line_from'],
$issue_data['column_from'],
($issue_data['severity'] === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data['message']
$issue_data->file_path,
$issue_data->line_from,
$issue_data->column_from,
($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data->message
) . "\n";
}

View File

@ -16,11 +16,11 @@ class GithubActionsReport extends Report
foreach ($this->issues_data as $issue_data) {
$output .= sprintf(
'::%s file=%s,line=%s,col=%s::%s',
($issue_data['severity'] === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data['file_name'],
$issue_data['line_from'],
$issue_data['column_from'],
$issue_data['message']
($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data->file_name,
$issue_data->line_from,
$issue_data->column_from,
$issue_data->message
) . "\n";
}

View File

@ -14,7 +14,7 @@ class JsonSummaryReport extends Report
$type_counts = [];
foreach ($this->issues_data as $issue_data) {
$type = $issue_data['type'];
$type = $issue_data->type;
if (!isset($type_counts[$type])) {
$type_counts[$type] = 0;

View File

@ -30,8 +30,8 @@ class JunitReport extends Report
$ndata = [];
foreach ($this->issues_data as $error) {
$is_error = $error['severity'] === Config::REPORT_ERROR;
$is_warning = $error['severity'] === Config::REPORT_INFO;
$is_error = $error->severity === Config::REPORT_ERROR;
$is_warning = $error->severity === Config::REPORT_INFO;
if ($is_error) {
$errors++;
@ -44,7 +44,7 @@ class JunitReport extends Report
$tests++;
$fname = $error['file_name'];
$fname = $error->file_name;
if (!isset($ndata[$fname])) {
$ndata[$fname] = [
@ -160,16 +160,6 @@ class JunitReport extends Report
}
/**
* @param array{
* line_from: int,
* type: string,
* message: string,
* selected_text: string,
* snippet: string,
* column_from: int,
* column_to: int
* } $issue_data
*
* @return array{
* data: array{
* column_from: int,
@ -183,18 +173,18 @@ class JunitReport extends Report
* type: string
* }
*/
private function createFailure(array $issue_data) : array
private function createFailure(\Psalm\Internal\Analyzer\IssueData $issue_data) : array
{
return [
'type' => $issue_data['type'],
'type' => $issue_data->type,
'data' => [
'message' => $issue_data['message'],
'type' => $issue_data['type'],
'snippet' => $issue_data['snippet'],
'selected_text' => $issue_data['selected_text'],
'line' => $issue_data['line_from'],
'column_from' => $issue_data['column_from'],
'column_to' => $issue_data['column_to'],
'message' => $issue_data->message,
'type' => $issue_data->type,
'snippet' => $issue_data->snippet,
'selected_text' => $issue_data->selected_text,
'line' => $issue_data->line_from,
'column_from' => $issue_data->column_from,
'column_to' => $issue_data->column_to,
],
];
}

View File

@ -21,21 +21,17 @@ class PylintReport extends Report
}
/**
* @param array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int} $issue_data
*
* @return string
*/
private function format(array $issue_data): string
private function format(\Psalm\Internal\Analyzer\IssueData $issue_data): string
{
$message = sprintf(
'%s: %s',
$issue_data['type'],
$issue_data['message']
$issue_data->type,
$issue_data->message
);
if ($issue_data['severity'] === Config::REPORT_ERROR) {
if ($issue_data->severity === Config::REPORT_ERROR) {
$code = 'E0001';
} else {
$code = 'W0001';
@ -44,11 +40,11 @@ class PylintReport extends Report
// https://docs.pylint.org/en/1.6.0/output.html doesn't mention what to do about 'column',
// but it's still useful for users.
// E.g. jenkins can't parse %s:%d:%d.
$message = sprintf('%s (column %d)', $message, $issue_data['column_from']);
$message = sprintf('%s (column %d)', $message, $issue_data->column_from);
$issue_string = sprintf(
'%s:%d: [%s] %s',
$issue_data['file_name'],
$issue_data['line_from'],
$issue_data->file_name,
$issue_data->line_from,
$code,
$message
);

View File

@ -24,20 +24,20 @@ class SonarqubeReport extends Report
foreach ($this->issues_data as $issue_data) {
$report['issues'][] = [
'engineId' => 'Psalm',
'ruleId' => $issue_data['type'],
'ruleId' => $issue_data->type,
'primaryLocation' => [
'message' => $issue_data['message'],
'filePath' => $issue_data['file_name'],
'message' => $issue_data->message,
'filePath' => $issue_data->file_name,
'textRange' => [
'startLine' => $issue_data['line_from'],
'endLine' => $issue_data['line_to'],
'startLine' => $issue_data->line_from,
'endLine' => $issue_data->line_to,
// Columns in external issue reports are indexed from 0
'startColumn' => max(0, $issue_data['column_from'] - 1),
'endColumn' => max(0, $issue_data['column_to'] - 1),
'startColumn' => max(0, $issue_data->column_from - 1),
'endColumn' => max(0, $issue_data->column_to - 1),
],
],
'type' => 'CODE_SMELL',
'severity' => $issue_data['severity'] == Config::REPORT_ERROR ? 'CRITICAL' : 'MINOR',
'severity' => $issue_data->severity == Config::REPORT_ERROR ? 'CRITICAL' : 'MINOR',
];
}

View File

@ -16,12 +16,12 @@ class TextReport extends Report
foreach ($this->issues_data as $issue_data) {
$output .= sprintf(
'%s:%s:%s:%s - %s: %s',
$issue_data['file_path'],
$issue_data['line_from'],
$issue_data['column_from'],
($issue_data['severity'] === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data['type'],
$issue_data['message']
$issue_data->file_path,
$issue_data->line_from,
$issue_data->column_from,
($issue_data->severity === Config::REPORT_ERROR ? 'error' : 'warning'),
$issue_data->type,
$issue_data->message
) . "\n";
}

View File

@ -2,6 +2,7 @@
namespace Psalm\Test\Config\Plugin\Hook;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\IssueData;
use Psalm\Plugin\Hook\{
AfterAnalysisInterface
};
@ -14,9 +15,7 @@ class AfterAnalysis implements
/**
* Called after analysis is complete
*
* @param array<string, list<array{severity: string, line_from: int, line_to: int, type: string, message: string,
* file_name: string, file_path: string, snippet: string, from: int, to: int,
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
* @param array<string, list<IssueData>> $issues
*
* @return void
*/

View File

@ -141,62 +141,144 @@ class ErrorBaselineTest extends TestCase
$baselineFile,
[
'sample/sample-file.php' => [
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'foo',
],
[
'file_name' => 'sample\sample-file.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'bar',
],
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'bat',
],
[
'file_name' => 'sample\sample-file.php',
'type' => 'MixedOperand',
'severity' => 'error',
'selected_text' => 'bing',
],
[
'file_name' => 'sample/sample-file.php',
'type' => 'AssignmentToVoid',
'severity' => 'info',
'selected_text' => 'bong',
],
[
'file_name' => 'sample\sample-file.php',
'type' => 'CircularReference',
'severity' => 'suppress',
'selected_text' => 'birdy',
],
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'foo',
'foo',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bar',
'bar',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bat',
'bat',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedOperand',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bing',
'bing',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'info',
0,
0,
'AssignmentToVoid',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bong',
'bong',
0,
0,
0,
0,
0,
0,
),
],
'sample/sample-file2.php' => [
[
'file_name' => 'sample/sample-file2.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'boardy',
],
[
'file_name' => 'sample\sample-file2.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'bardy',
],
[
'file_name' => 'sample/sample-file2.php',
'type' => 'TypeCoercion',
'severity' => 'error',
'selected_text' => 'hardy' . "\n",
],
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file2.php',
'sample/sample-file2.php',
'boardy',
'boardy',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file2.php',
'sample/sample-file2.php',
'bardy',
'bardy',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'TypeCoercion',
'Message',
'sample/sample-file2.php',
'sample/sample-file2.php',
'hardy' . "\n",
'hardy' . "\n",
0,
0,
0,
0,
0,
0,
),
],
],
false
@ -260,38 +342,93 @@ class ErrorBaselineTest extends TestCase
$newIssues = [
'sample/sample-file.php' => [
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'foo',
],
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedAssignment',
'severity' => 'error',
'selected_text' => 'bar',
],
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedOperand',
'severity' => 'error',
'selected_text' => 'bat',
],
[
'file_name' => 'sample/sample-file.php',
'type' => 'MixedOperand',
'severity' => 'error',
'selected_text' => 'bam',
],
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'foo',
'foo',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedAssignment',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bar',
'bar',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedOperand',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bat',
'bat',
0,
0,
0,
0,
0,
0,
),
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'MixedOperand',
'Message',
'sample/sample-file.php',
'sample/sample-file.php',
'bam',
'bam',
0,
0,
0,
0,
0,
0,
),
],
'sample/sample-file2.php' => [
[
'file_name' => 'sample/sample-file2.php',
'type' => 'TypeCoercion',
'severity' => 'error',
'selected_text' => 'tar',
],
new \Psalm\Internal\Analyzer\IssueData(
'error',
0,
0,
'TypeCoercion',
'Message',
'sample/sample-file2.php',
'sample/sample-file2.php',
'tar',
'tar',
0,
0,
0,
0,
0,
0,
),
],
];

View File

@ -92,7 +92,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
$found_positions[] = $issue_data->from;
}
}
@ -118,7 +118,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
$found_positions[] = $issue_data->from;
}
}
@ -146,7 +146,7 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
$found_positions[] = $issue_data->from;
}
}

View File

@ -20,42 +20,42 @@ class IssueBufferTest extends TestCase
IssueBuffer::clear();
IssueBuffer::addIssues([
'/path/one.php' => [
[
"severity" => "error",
"type" => "MissingPropertyType",
"message" => 'Message',
"file_name" =>"one.php",
"file_path" => "/path/one.php",
"snippet" => "snippet-1",
"selected_text" => "snippet-1",
"from"=> 0,
"to"=> 0,
"snippet_from" => 0,
"snippet_to" => 0,
"column_from" => 0,
"column_to" => 0,
"line_from" => 0,
"line_to" => 0,
]
new \Psalm\Internal\Analyzer\IssueData(
"error",
0,
0,
"MissingPropertyType",
'Message',
"one.php",
"/path/one.php",
"snippet-1",
"snippet-1",
0,
0,
0,
0,
0,
0,
)
],
'/path/two.php' => [
[
"severity" => "error",
"type" => "MissingPropertyType",
"message" => 'Message',
"file_name" =>"two.php",
"file_path" => "/path/two.php",
"snippet" => "snippet-2",
"selected_text" => "snippet-2",
"from"=> 0,
"to"=> 0,
"snippet_from" => 0,
"snippet_to" => 0,
"column_from" => 0,
"column_to" => 0,
"line_from" => 0,
"line_to" => 0,
]
new \Psalm\Internal\Analyzer\IssueData(
"error",
0,
0,
"MissingPropertyType",
'Message',
"two.php",
"/path/two.php",
"snippet-2",
"snippet-2",
0,
0,
0,
0,
0,
0,
)
]
]);
$baseline = [

View File

@ -54,13 +54,13 @@ class JsonOutputTest extends TestCase
$this->analyzeFile('somefile.php', new Context());
$issue_data = IssueBuffer::getIssuesData()['somefile.php'][0];
$this->assertSame('somefile.php', $issue_data['file_path']);
$this->assertSame('error', $issue_data['severity']);
$this->assertSame($message, $issue_data['message']);
$this->assertSame($line_number, $issue_data['line_from']);
$this->assertSame('somefile.php', $issue_data->file_path);
$this->assertSame('error', $issue_data->severity);
$this->assertSame($message, $issue_data->message);
$this->assertSame($line_number, $issue_data->line_from);
$this->assertSame(
$error,
substr($code, $issue_data['from'], $issue_data['to'] - $issue_data['from'])
substr($code, $issue_data->from, $issue_data->to - $issue_data->from)
);
}
@ -184,7 +184,12 @@ echo $a;';
'column_to' => 8,
],
],
$issue_data
\array_map(
function ($d) {
return (array) $d;
},
$issue_data
)
);
}

View File

@ -188,23 +188,23 @@ echo $a;';
public function testFilteredJsonReportIsStillArray(): void
{
$issues_data = [
22 => [
'severity' => 'info',
'line_from' => 15,
'line_to' => 15,
'type' => 'PossiblyUndefinedGlobalVariable',
'message' => 'Possibly undefined global variable $a, first seen on line 10',
'file_name' => 'somefile.php',
'file_path' => 'somefile.php',
'snippet' => 'echo $a',
'selected_text' => '$a',
'from' => 201,
'to' => 203,
'snippet_from' => 196,
'snippet_to' => 203,
'column_from' => 6,
'column_to' => 8,
],
22 => new \Psalm\Internal\Analyzer\IssueData(
'info',
15,
15,
'PossiblyUndefinedGlobalVariable',
'Possibly undefined global variable $a, first seen on line 10',
'somefile.php',
'somefile.php',
'echo $a',
'$a',
201,
203,
196,
203,
6,
8,
),
];
$report_options = ProjectAnalyzer::getFileReportOptions([__DIR__ . '/test-report.json'])[0];