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

Index issues by file to reduce some lookups

This commit is contained in:
Matthew Brown 2020-01-21 21:07:44 -05:00
parent 0132b3789b
commit 8202af957d
12 changed files with 193 additions and 153 deletions

View File

@ -51,7 +51,12 @@ class ErrorBaseline
/**
* @param FileProvider $fileProvider
* @param string $baselineFile
* @param array<array{file_name: string, type: string, severity: string, selected_text: string}> $issues
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
*
* @return void
*/
@ -129,7 +134,12 @@ class ErrorBaseline
/**
* @param FileProvider $fileProvider
* @param string $baselineFile
* @param array<array{file_name: string, type: string, severity: string, selected_text: string}> $issues
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
*
* @throws Exception\ConfigException
*
@ -177,14 +187,27 @@ class ErrorBaseline
}
/**
* @param array<array{file_name: string, type: string, severity: string, selected_text: string}> $issues
* @param array<string, list<array{
* file_name: string,
* type: string,
* severity: string,
* selected_text: string
* }>> $issues
*
* @return array<string,array<string,array{o:int, s:array<int, string>}>>
*/
private static function countIssueTypesByFile(array $issues): array
{
$normalized_data = [];
foreach ($issues as $file_issues) {
foreach ($file_issues as $issue_data) {
$normalized_data[] = $issue_data;
}
}
$groupedIssues = array_reduce(
$issues,
$normalized_data,
/**
* @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

View File

@ -1115,7 +1115,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
);
if ($existing_issues) {
IssueBuffer::addIssues($existing_issues);
IssueBuffer::addIssues([$this->getFilePath() => $existing_issues]);
continue;
}
}
@ -1623,7 +1623,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer
$end
);
IssueBuffer::addIssues($existing_issues);
IssueBuffer::addIssues([$source->getFilePath() => $existing_issues]);
return $method_analyzer;
}

View File

@ -46,7 +46,7 @@ use function usort;
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: string}>
*
* @psalm-type WorkerData = array{
* issues: array<int, IssueData>,
* issues: array<string, list<IssueData>>,
* fixable_issue_counts: array<string, int>,
* file_references_to_classes: array<string, array<string,bool>>,
* file_references_to_class_members: array<string, array<string,bool>>,
@ -337,7 +337,7 @@ class Analyzer
$file_analyzer->clearSourceBeforeDestruction();
unset($file_analyzer);
return $this->getFileIssues($file_path);
return IssueBuffer::getIssuesDataForFile($file_path);
};
$task_done_closure =
@ -479,8 +479,10 @@ class Analyzer
continue;
}
foreach ($pool_data['issues'] as $issue_data) {
$codebase->file_reference_provider->addIssue($issue_data['file_path'], $issue_data);
foreach ($pool_data['issues'] as $file_path => $file_issues) {
foreach ($file_issues as $issue_data) {
$codebase->file_reference_provider->addIssue($file_path, $issue_data);
}
}
$codebase->file_reference_provider->addFileReferencesToClasses(
@ -567,32 +569,18 @@ class Analyzer
$analysis_worker($i, $file_path);
++$i;
$issues = $this->getFileIssues($file_path);
$issues = IssueBuffer::getIssuesDataForFile($file_path);
$task_done_closure($issues);
}
foreach (IssueBuffer::getIssuesData() as $issue_data) {
$codebase->file_reference_provider->addIssue($issue_data['file_path'], $issue_data);
foreach (IssueBuffer::getIssuesData() as $file_path => $file_issues) {
foreach ($file_issues as $issue_data) {
$codebase->file_reference_provider->addIssue($file_path, $issue_data);
}
}
}
}
/**
* @return array<IssueData>
*/
private function getFileIssues(string $file_path): array
{
return array_filter(
IssueBuffer::getIssuesData(),
/**
* @param array{file_path: string} $issue
*/
function (array $issue) use ($file_path): bool {
return $issue['file_path'] === $file_path;
}
);
}
/**
* @return void
*/
@ -1352,7 +1340,7 @@ class Analyzer
* @param int $start
* @param int $end
*
* @return array<int, IssueData>
* @return list<IssueData>
*/
public function getExistingIssuesForFile($file_path, $start, $end, ?string $issue_type = null)
{

View File

@ -65,7 +65,7 @@ use function substr;
* 7:array<string, bool>,
* 8:array<string, bool>
* },
* issues:array<int, IssueData>,
* issues:array<string, list<IssueData>>,
* changed_members:array<string, array<string, bool>>,
* unchanged_signature_members:array<string, array<string, bool>>,
* diff_map:array<string, array<int, array{0:int, 1:int, 2:int, 3:int}>>,

View File

@ -325,16 +325,6 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
$data = \Psalm\IssueBuffer::clear();
foreach ($uris as $file_path => $uri) {
$data = array_values(array_filter(
$data,
/**
* @param array{file_path: string} $issue_data
*/
function (array $issue_data) use ($file_path) : bool {
return $issue_data['file_path'] === $file_path;
}
));
$diagnostics = array_map(
/**
* @param array{
@ -378,7 +368,7 @@ class LanguageServer extends AdvancedJsonRpc\Dispatcher
'Psalm'
);
},
$data
$data[$file_path] ?? []
);
$this->client->textDocument->publishDiagnostics($uri, $diagnostics);

View File

@ -33,9 +33,9 @@ use function usort;
class IssueBuffer
{
/**
* @var array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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}>
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>>
*/
protected static $issues_data = [];
@ -208,7 +208,7 @@ class IssueBuffer
if ($reporting_level === Config::REPORT_INFO) {
if (!self::alreadyEmitted($emitted_key)) {
self::$issues_data[] = $e->toArray(Config::REPORT_INFO);
self::$issues_data[$e->getFilePath()][] = $e->toArray(Config::REPORT_INFO);
}
return false;
@ -227,7 +227,7 @@ class IssueBuffer
if (!self::alreadyEmitted($emitted_key)) {
++self::$error_count;
self::$issues_data[] = $e->toArray(Config::REPORT_ERROR);
self::$issues_data[$e->getFilePath()][] = $e->toArray(Config::REPORT_ERROR);
}
if ($is_fixable) {
@ -247,15 +247,25 @@ class IssueBuffer
}
/**
* @return array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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}>
* column_from: int, column_to: int, selected_text: string}>>
*/
public static function getIssuesData()
{
return self::$issues_data;
}
/**
* @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}>
*/
public static function getIssuesDataForFile(string $file_path)
{
return self::$issues_data[$file_path] ?? [];
}
/**
* @return array<string, int>
*/
@ -357,22 +367,24 @@ class IssueBuffer
}
/**
* @param array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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
* snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues_data
*
* @return void
*/
public static function addIssues(array $issues_data)
{
foreach ($issues_data as $issue) {
$emitted_key = $issue['type']
. '-' . $issue['file_name']
. ':' . $issue['line_from']
. ':' . $issue['column_from'];
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'];
if (!self::alreadyEmitted($emitted_key)) {
self::$issues_data[] = $issue;
if (!self::alreadyEmitted($emitted_key)) {
self::$issues_data[$file_path][] = $issue;
}
}
}
}
@ -407,65 +419,64 @@ class IssueBuffer
$info_count = 0;
if (self::$issues_data) {
usort(
self::$issues_data,
/**
* @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']) {
return 0;
\ksort(self::$issues_data);
foreach (self::$issues_data as &$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']) {
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;
}
);
);
}
if (!empty($issue_baseline)) {
// Set severity for issues in baseline to INFO
foreach (self::$issues_data as $key => $issue_data) {
$file = $issue_data['file_name'];
$file = str_replace('\\', '/', $file);
$type = $issue_data['type'];
foreach (self::$issues_data as $file_path => $file_issues) {
foreach ($file_issues as $key => $issue_data) {
$file = $issue_data['file_name'];
$file = str_replace('\\', '/', $file);
$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_baseline[$file][$type]['s'],
true
);
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_baseline[$file][$type]['s'],
true
);
if ($position !== false) {
if ($position !== false) {
$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;
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_baseline[$file][$type]['o'] = $issue_baseline[$file][$type]['o'] - 1;
}
/** @psalm-suppress PropertyTypeCoercion due to Psalm bug */
self::$issues_data[$file_path][$key] = $issue_data;
}
self::$issues_data[$key] = $issue_data;
}
}
foreach (self::$issues_data as $issue_data) {
if ($issue_data['severity'] === Config::REPORT_ERROR) {
++$error_count;
} else {
++$info_count;
}
}
@ -475,6 +486,16 @@ class IssueBuffer
);
}
foreach (self::$issues_data as $file_issues) {
foreach ($file_issues as $issue_data) {
if ($issue_data['severity'] === Config::REPORT_ERROR) {
++$error_count;
} else {
++$info_count;
}
}
}
$after_analysis_hooks = $codebase->config->after_analysis;
if ($after_analysis_hooks) {
@ -488,6 +509,7 @@ class IssueBuffer
}
foreach ($after_analysis_hooks as $after_analysis_hook) {
/** @psalm-suppress ArgumentTypeCoercion due to Psalm bug */
$after_analysis_hook::afterAnalysis(
$codebase,
self::$issues_data,
@ -597,26 +619,34 @@ class IssueBuffer
$total_expression_count = $mixed_counts[0] + $mixed_counts[1];
$mixed_expression_count = $mixed_counts[0];
$normalized_data = [];
foreach (self::$issues_data as $file_issues) {
foreach ($file_issues as $issue_data) {
$normalized_data[] = $issue_data;
}
}
switch ($report_options->format) {
case Report::TYPE_COMPACT:
$output = new CompactReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new CompactReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_EMACS:
$output = new EmacsReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new EmacsReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_TEXT:
$output = new TextReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new TextReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_JSON:
$output = new JsonReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new JsonReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_JSON_SUMMARY:
$output = new JsonSummaryReport(
self::$issues_data,
$normalized_data,
self::$fixable_issue_counts,
$report_options,
$mixed_expression_count,
@ -625,27 +655,27 @@ class IssueBuffer
break;
case Report::TYPE_SONARQUBE:
$output = new SonarqubeReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new SonarqubeReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_PYLINT:
$output = new PylintReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new PylintReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_CHECKSTYLE:
$output = new CheckstyleReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new CheckstyleReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_XML:
$output = new XmlReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new XmlReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_JUNIT:
$output = new JUnitReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new JUnitReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
case Report::TYPE_CONSOLE:
$output = new ConsoleReport(self::$issues_data, self::$fixable_issue_counts, $report_options);
$output = new ConsoleReport($normalized_data, self::$fixable_issue_counts, $report_options);
break;
}
@ -686,9 +716,9 @@ class IssueBuffer
}
/**
* @return array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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}>
* column_from: int, column_to: int}>>
*/
public static function clear()
{

View File

@ -9,9 +9,9 @@ interface AfterAnalysisInterface
/**
* Called after analysis is complete
*
* @param array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
*
* @return void
*/

View File

@ -29,9 +29,9 @@ class Shepherd implements \Psalm\Plugin\Hook\AfterAnalysisInterface
/**
* Called after analysis is complete
*
* @param array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
*
* @return void
*/
@ -56,18 +56,20 @@ class Shepherd implements \Psalm\Plugin\Hook\AfterAnalysisInterface
unset($build_info['git']);
if ($build_info) {
$normalized_data = [];
foreach ($issues as $file_issues) {
foreach ($file_issues as $issue_data) {
if ($issue_data['severity'] === 'error') {
$normalized_data[] = $issue_data;
}
}
}
$data = [
'build' => $build_info,
'git' => $source_control_data,
'issues' => array_filter(
$issues,
/**
* @param array{severity: string} $i
*/
function (array $i) : bool {
return $i['severity'] === 'error';
}
),
'issues' => $normalized_data,
'coverage' => $codebase->analyzer->getTotalTypeCoverage($codebase),
];

View File

@ -14,9 +14,9 @@ class AfterAnalysis implements
/**
* Called after analysis is complete
*
* @param array<int, array{severity: string, line_from: int, line_to: int, type: string, message: string,
* @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
* snippet_from: int, snippet_to: int, column_from: int, column_to: int, selected_text: string}>> $issues
*
* @return void
*/

View File

@ -85,9 +85,15 @@ class ErrorFixTest extends \Psalm\Tests\TestCase
$codebase->analyzer->analyzeFiles($this->project_analyzer, 1, false);
$expected_count = 0;
$data = \Psalm\IssueBuffer::clear();
$this->assertSame($error_counts[$i], count($data));
foreach ($data as $file_issues) {
$expected_count += count($file_issues);
}
$this->assertSame($error_counts[$i], $expected_count);
}
}

View File

@ -88,13 +88,13 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
$data = \Psalm\IssueBuffer::clear();
$found_positions = array_map(
/** @param array{from: int} $a */
function (array $a) : int {
return $a['from'];
},
$data
);
$found_positions = [];
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
}
}
$this->assertSame($error_positions[0], $found_positions);
@ -112,13 +112,13 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
$data = \Psalm\IssueBuffer::clear();
$found_positions = array_map(
/** @param array{from: int} $a */
function (array $a) : int {
return $a['from'];
},
$data
);
$found_positions = [];
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
}
}
$this->assertSame($error_positions[$i + 1], $found_positions);
}
@ -140,13 +140,13 @@ class TemporaryUpdateTest extends \Psalm\Tests\TestCase
$data = \Psalm\IssueBuffer::clear();
$found_positions = array_map(
/** @param array{from: int} $a */
function (array $a) : int {
return $a['from'];
},
$data
);
$found_positions = [];
foreach ($data as $file_issues) {
foreach ($file_issues as $issue_data) {
$found_positions[] = $issue_data['from'];
}
}
$this->assertSame($error_positions[count($file_stages)], $found_positions);
}

View File

@ -52,7 +52,7 @@ class JsonOutputTest extends TestCase
{
$this->addFile('somefile.php', $code);
$this->analyzeFile('somefile.php', new Context());
$issue_data = IssueBuffer::getIssuesData()[0];
$issue_data = IssueBuffer::getIssuesData()['somefile.php'][0];
$this->assertSame('somefile.php', $issue_data['file_path']);
$this->assertSame('error', $issue_data['severity']);
@ -94,7 +94,8 @@ echo $a;';
$this->analyzeFile('somefile.php', new Context());
$issue_data = IssueBuffer::getIssuesData();
$issue_data = IssueBuffer::getIssuesData()['somefile.php'];
$this->assertSame(
[
[