2018-02-04 00:52:35 +01:00
|
|
|
<?php
|
2018-11-12 16:46:55 +01:00
|
|
|
namespace Psalm\Internal\Codebase;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
use function array_filter;
|
|
|
|
use function array_intersect_key;
|
|
|
|
use function array_merge;
|
|
|
|
use function count;
|
|
|
|
use function explode;
|
2020-07-07 23:10:51 +02:00
|
|
|
use InvalidArgumentException;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function number_format;
|
|
|
|
use function pathinfo;
|
2018-10-07 02:11:19 +02:00
|
|
|
use PhpParser;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function preg_replace;
|
2018-02-04 00:52:35 +01:00
|
|
|
use Psalm\Config;
|
2018-11-12 16:46:55 +01:00
|
|
|
use Psalm\FileManipulation;
|
2020-02-17 00:24:40 +01:00
|
|
|
use Psalm\Internal\Analyzer\IssueData;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\Internal\Analyzer\FileAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\ProjectAnalyzer;
|
2020-08-25 01:24:27 +02:00
|
|
|
use Psalm\Internal\FileManipulation\ClassDocblockManipulator;
|
2018-11-12 16:46:55 +01:00
|
|
|
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
|
|
|
use Psalm\Internal\FileManipulation\FunctionDocblockManipulator;
|
2020-07-08 18:03:12 +02:00
|
|
|
use Psalm\Internal\FileManipulation\PropertyDocblockManipulator;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Provider\FileProvider;
|
|
|
|
use Psalm\Internal\Provider\FileStorageProvider;
|
2019-07-05 22:24:00 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2019-05-30 16:30:41 +02:00
|
|
|
use Psalm\Progress\Progress;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function strpos;
|
2019-07-05 22:24:00 +02:00
|
|
|
use function substr;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function usort;
|
2020-03-26 19:43:48 +01:00
|
|
|
use function array_values;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @psalm-type TaggedCodeType = array<int, array{0: int, 1: non-empty-string}>
|
2018-10-26 22:17:15 +02:00
|
|
|
*
|
2020-05-15 06:16:20 +02:00
|
|
|
* @psalm-type FileMapType = array{
|
|
|
|
* 0: TaggedCodeType,
|
|
|
|
* 1: TaggedCodeType,
|
2020-05-15 16:18:05 +02:00
|
|
|
* 2: array<int, array{0: int, 1: non-empty-string, 2: int}>
|
2020-05-15 06:16:20 +02:00
|
|
|
* }
|
|
|
|
*
|
2018-09-26 22:33:59 +02:00
|
|
|
* @psalm-type WorkerData = array{
|
2020-01-22 03:07:44 +01:00
|
|
|
* issues: array<string, list<IssueData>>,
|
2019-12-02 21:24:01 +01:00
|
|
|
* fixable_issue_counts: array<string, int>,
|
2020-04-02 23:17:55 +02:00
|
|
|
* nonmethod_references_to_classes: array<string, array<string,bool>>,
|
2020-03-26 17:35:27 +01:00
|
|
|
* method_references_to_classes: array<string, array<string,bool>>,
|
2019-06-21 23:10:35 +02:00
|
|
|
* file_references_to_class_members: array<string, array<string,bool>>,
|
|
|
|
* file_references_to_missing_class_members: array<string, array<string,bool>>,
|
|
|
|
* mixed_counts: array<string, array{0: int, 1: int}>,
|
|
|
|
* mixed_member_names: array<string, array<string, bool>>,
|
2020-07-30 21:30:19 +02:00
|
|
|
* function_timings: array<string, float>,
|
2019-06-21 23:10:35 +02:00
|
|
|
* file_manipulations: array<string, FileManipulation[]>,
|
|
|
|
* method_references_to_class_members: array<string, array<string,bool>>,
|
|
|
|
* method_references_to_missing_class_members: array<string, array<string,bool>>,
|
|
|
|
* method_param_uses: array<string, array<int, array<string, bool>>>,
|
|
|
|
* analyzed_methods: array<string, array<string, int>>,
|
2020-05-15 06:16:20 +02:00
|
|
|
* file_maps: array<string, FileMapType>,
|
2019-06-21 23:10:35 +02:00
|
|
|
* class_locations: array<string, array<int, \Psalm\CodeLocation>>,
|
|
|
|
* class_method_locations: array<string, array<int, \Psalm\CodeLocation>>,
|
|
|
|
* class_property_locations: array<string, array<int, \Psalm\CodeLocation>>,
|
2019-08-04 16:37:36 +02:00
|
|
|
* possible_method_param_types: array<string, array<int, \Psalm\Type\Union>>,
|
2019-08-18 20:27:50 +02:00
|
|
|
* taint_data: ?\Psalm\Internal\Codebase\Taint,
|
2019-09-16 17:58:42 +02:00
|
|
|
* unused_suppressions: array<string, array<int, int>>,
|
2020-08-24 00:05:48 +02:00
|
|
|
* used_suppressions: array<string, array<int, bool>>,
|
2020-08-25 01:24:27 +02:00
|
|
|
* function_docblock_manipulators: array<string, array<int, FunctionDocblockManipulator>>,
|
|
|
|
* mutable_classes: array<string, bool>,
|
2018-09-26 22:33:59 +02:00
|
|
|
* }
|
2018-09-26 00:37:24 +02:00
|
|
|
*/
|
|
|
|
|
2018-02-09 23:51:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*
|
|
|
|
* Called in the analysis phase of Psalm's execution
|
|
|
|
*/
|
2018-02-04 00:52:35 +01:00
|
|
|
class Analyzer
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var Config
|
|
|
|
*/
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var FileProvider
|
|
|
|
*/
|
|
|
|
private $file_provider;
|
|
|
|
|
2018-06-04 00:31:43 +02:00
|
|
|
/**
|
|
|
|
* @var FileStorageProvider
|
|
|
|
*/
|
|
|
|
private $file_storage_provider;
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
2019-05-30 16:30:41 +02:00
|
|
|
* @var Progress
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2019-05-30 16:30:41 +02:00
|
|
|
private $progress;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Used to store counts of mixed vs non-mixed variables
|
|
|
|
*
|
2019-06-15 23:57:40 +02:00
|
|
|
* @var array<string, array{0: int, 1: int}>
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
|
|
|
private $mixed_counts = [];
|
|
|
|
|
2019-04-17 17:12:18 +02:00
|
|
|
/**
|
|
|
|
* Used to store member names of mixed property/method access
|
|
|
|
*
|
2019-04-27 23:38:24 +02:00
|
|
|
* @var array<string, array<string, bool>>
|
2019-04-17 17:12:18 +02:00
|
|
|
*/
|
|
|
|
private $mixed_member_names = [];
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
private $count_mixed = true;
|
|
|
|
|
2020-07-30 21:30:19 +02:00
|
|
|
/**
|
|
|
|
* Used to store debug performance data
|
|
|
|
*
|
|
|
|
* @var array<string, float>
|
|
|
|
*/
|
|
|
|
private $function_timings = [];
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* We analyze more files than we necessarily report errors in
|
|
|
|
*
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_to_analyze = [];
|
|
|
|
|
2020-03-26 19:22:06 +01:00
|
|
|
/**
|
|
|
|
* We can show analysis results on more files than we analyze
|
|
|
|
* because the results can be cached
|
|
|
|
*
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_with_analysis_results = [];
|
|
|
|
|
2019-04-26 17:23:19 +02:00
|
|
|
/**
|
|
|
|
* We may update fewer files than we analyse (i.e. for dead code detection)
|
|
|
|
*
|
|
|
|
* @var array<string>|null
|
|
|
|
*/
|
|
|
|
private $files_to_update = null;
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
2018-10-07 04:58:21 +02:00
|
|
|
* @var array<string, array<string, int>>
|
2018-09-26 00:37:24 +02:00
|
|
|
*/
|
2018-11-02 02:52:39 +01:00
|
|
|
private $analyzed_methods = [];
|
2018-09-26 00:37:24 +02:00
|
|
|
|
2018-09-26 22:33:59 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, array<int, IssueData>>
|
|
|
|
*/
|
|
|
|
private $existing_issues = [];
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @var array<string, array<int, array{0: int, 1: non-empty-string}>>
|
2018-10-26 22:17:15 +02:00
|
|
|
*/
|
|
|
|
private $reference_map = [];
|
|
|
|
|
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @var array<string, array<int, array{0: int, 1: non-empty-string}>>
|
2018-10-26 22:17:15 +02:00
|
|
|
*/
|
|
|
|
private $type_map = [];
|
|
|
|
|
2019-07-01 21:54:33 +02:00
|
|
|
/**
|
2020-05-15 16:18:05 +02:00
|
|
|
* @var array<string, array<int, array{0: int, 1: non-empty-string, 2: int}>>
|
2019-07-01 21:54:33 +02:00
|
|
|
*/
|
|
|
|
private $argument_map = [];
|
|
|
|
|
2019-05-31 17:55:24 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, array<int, \Psalm\Type\Union>>
|
|
|
|
*/
|
|
|
|
public $possible_method_param_types = [];
|
|
|
|
|
2020-08-25 01:24:27 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
public $mutable_classes = [];
|
|
|
|
|
2018-06-04 00:31:43 +02:00
|
|
|
public function __construct(
|
|
|
|
Config $config,
|
|
|
|
FileProvider $file_provider,
|
|
|
|
FileStorageProvider $file_storage_provider,
|
2019-05-30 16:30:41 +02:00
|
|
|
Progress $progress
|
2018-06-04 00:31:43 +02:00
|
|
|
) {
|
2018-02-04 00:52:35 +01:00
|
|
|
$this->config = $config;
|
|
|
|
$this->file_provider = $file_provider;
|
2018-06-04 00:31:43 +02:00
|
|
|
$this->file_storage_provider = $file_storage_provider;
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress = $progress;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<string, string> $files_to_analyze
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-03-26 19:22:06 +01:00
|
|
|
public function addFilesToAnalyze(array $files_to_analyze)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$this->files_to_analyze += $files_to_analyze;
|
2020-03-26 19:22:06 +01:00
|
|
|
$this->files_with_analysis_results += $files_to_analyze;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<string, string> $files_to_analyze
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addFilesToShowResults(array $files_to_analyze)
|
|
|
|
{
|
|
|
|
$this->files_with_analysis_results += $files_to_analyze;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-04-26 17:23:19 +02:00
|
|
|
/**
|
|
|
|
* @param array<string> $files_to_update
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setFilesToUpdate(array $files_to_update)
|
|
|
|
{
|
|
|
|
$this->files_to_update = $files_to_update;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function canReportIssues($file_path): bool
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2020-03-26 19:22:06 +01:00
|
|
|
return isset($this->files_with_analysis_results[$file_path]);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2019-01-02 15:00:45 +01:00
|
|
|
* @param array<string, class-string<FileAnalyzer>> $filetype_analyzers
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
private function getFileAnalyzer(
|
|
|
|
ProjectAnalyzer $project_analyzer,
|
|
|
|
$file_path,
|
|
|
|
array $filetype_analyzers
|
|
|
|
): FileAnalyzer {
|
2018-10-19 19:13:55 +02:00
|
|
|
$extension = (string) (pathinfo($file_path)['extension'] ?? '');
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
$file_name = $this->config->shortenFileName($file_path);
|
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
if (isset($filetype_analyzers[$extension])) {
|
|
|
|
$file_analyzer = new $filetype_analyzers[$extension]($project_analyzer, $file_path, $file_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_analyzer = new FileAnalyzer($project_analyzer, $file_path, $file_name);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Getting ' . $file_path . "\n");
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
return $file_analyzer;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-04-01 18:56:32 +02:00
|
|
|
public function analyzeFiles(
|
|
|
|
ProjectAnalyzer $project_analyzer,
|
|
|
|
int $pool_size,
|
|
|
|
bool $alter_code,
|
|
|
|
bool $consolidate_analyzed_data = false
|
|
|
|
) {
|
2018-11-11 18:01:14 +01:00
|
|
|
$this->loadCachedResults($project_analyzer);
|
2018-09-26 22:33:59 +02:00
|
|
|
|
2018-11-11 18:19:53 +01:00
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-06-03 05:33:57 +02:00
|
|
|
if ($alter_code) {
|
|
|
|
$project_analyzer->interpretRefactors();
|
|
|
|
}
|
|
|
|
|
2019-07-11 17:07:39 +02:00
|
|
|
$this->files_to_analyze = array_filter(
|
|
|
|
$this->files_to_analyze,
|
|
|
|
function (string $file_path) : bool {
|
|
|
|
return $this->file_provider->fileExists($file_path);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
$this->doAnalysis($project_analyzer, $pool_size);
|
|
|
|
|
2019-10-14 05:30:01 +02:00
|
|
|
$scanned_files = $codebase->scanner->getScannedFiles();
|
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
if ($codebase->taint) {
|
2020-05-22 04:47:58 +02:00
|
|
|
$codebase->taint->connectSinksAndSources();
|
|
|
|
}
|
2019-08-04 16:37:36 +02:00
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
$this->progress->finish();
|
2020-03-31 15:56:27 +02:00
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
if ($consolidate_analyzed_data) {
|
|
|
|
$project_analyzer->consolidateAnalyzedData();
|
|
|
|
}
|
2020-03-31 15:56:27 +02:00
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
foreach (IssueBuffer::getIssuesData() as $file_path => $file_issues) {
|
|
|
|
$codebase->file_reference_provider->clearExistingIssuesForFile($file_path);
|
2020-04-01 21:30:35 +02:00
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
foreach ($file_issues as $issue_data) {
|
|
|
|
$codebase->file_reference_provider->addIssue($file_path, $issue_data);
|
2020-03-31 15:56:27 +02:00
|
|
|
}
|
2019-08-04 16:37:36 +02:00
|
|
|
}
|
|
|
|
|
2020-05-22 04:47:58 +02:00
|
|
|
$codebase->file_reference_provider->updateReferenceCache($codebase, $scanned_files);
|
|
|
|
|
2019-08-18 22:10:12 +02:00
|
|
|
if ($codebase->track_unused_suppressions) {
|
|
|
|
IssueBuffer::processUnusedSuppressions($codebase->file_provider);
|
|
|
|
}
|
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
$codebase->file_reference_provider->setAnalyzedMethods($this->analyzed_methods);
|
|
|
|
$codebase->file_reference_provider->setFileMaps($this->getFileMaps());
|
|
|
|
$codebase->file_reference_provider->setTypeCoverage($this->mixed_counts);
|
2019-10-14 05:37:15 +02:00
|
|
|
$codebase->file_reference_provider->updateReferenceCache($codebase, $scanned_files);
|
2019-08-04 16:37:36 +02:00
|
|
|
|
|
|
|
if ($codebase->diff_methods) {
|
|
|
|
$codebase->statements_provider->resetDiffs();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($alter_code) {
|
|
|
|
$this->progress->startAlteringFiles();
|
|
|
|
|
|
|
|
$project_analyzer->prepareMigration();
|
|
|
|
|
|
|
|
$files_to_update = $this->files_to_update !== null ? $this->files_to_update : $this->files_to_analyze;
|
|
|
|
|
|
|
|
foreach ($files_to_update as $file_path) {
|
|
|
|
$this->updateFile($file_path, $project_analyzer->dry_run);
|
|
|
|
}
|
|
|
|
|
|
|
|
$project_analyzer->migrateCode();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-30 21:30:19 +02:00
|
|
|
private function doAnalysis(ProjectAnalyzer $project_analyzer, int $pool_size) : void
|
2019-08-04 16:37:36 +02:00
|
|
|
{
|
|
|
|
$this->progress->start(count($this->files_to_analyze));
|
|
|
|
|
2020-04-05 06:37:46 +02:00
|
|
|
\ksort($this->files_to_analyze);
|
2020-04-05 06:28:55 +02:00
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
$filetype_analyzers = $this->config->getFiletypeAnalyzers();
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
$analysis_worker =
|
|
|
|
/**
|
2018-02-27 17:39:26 +01:00
|
|
|
* @param int $_
|
2018-02-04 00:52:35 +01:00
|
|
|
* @param string $file_path
|
|
|
|
*
|
2019-05-30 16:30:41 +02:00
|
|
|
* @return array
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2018-11-11 18:01:14 +01:00
|
|
|
function ($_, $file_path) use ($project_analyzer, $filetype_analyzers) {
|
|
|
|
$file_analyzer = $this->getFileAnalyzer($project_analyzer, $file_path, $filetype_analyzers);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Analyzing ' . $file_analyzer->getFilePath() . "\n");
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2018-11-11 18:01:14 +01:00
|
|
|
$file_analyzer->analyze(null);
|
2019-06-30 03:06:21 +02:00
|
|
|
$file_analyzer->context = null;
|
|
|
|
$file_analyzer->clearSourceBeforeDestruction();
|
|
|
|
unset($file_analyzer);
|
2019-05-30 16:30:41 +02:00
|
|
|
|
2020-01-22 03:07:44 +01:00
|
|
|
return IssueBuffer::getIssuesDataForFile($file_path);
|
2018-02-04 00:52:35 +01:00
|
|
|
};
|
|
|
|
|
2019-06-03 17:20:42 +02:00
|
|
|
$task_done_closure =
|
|
|
|
/**
|
|
|
|
* @param array<IssueData> $issues
|
|
|
|
*/
|
|
|
|
function (array $issues): void {
|
|
|
|
$has_error = false;
|
|
|
|
$has_info = false;
|
|
|
|
|
|
|
|
foreach ($issues as $issue) {
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue->severity === 'error') {
|
2019-06-03 17:20:42 +02:00
|
|
|
$has_error = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue->severity === 'info') {
|
2019-06-03 17:20:42 +02:00
|
|
|
$has_info = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->progress->taskDone($has_error ? 2 : ($has_info ? 1 : 0));
|
|
|
|
};
|
2019-05-30 16:30:41 +02:00
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
if ($pool_size > 1 && count($this->files_to_analyze) > $pool_size) {
|
2019-10-14 02:09:50 +02:00
|
|
|
$shuffle_count = $pool_size + 1;
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
$file_paths = \array_values($this->files_to_analyze);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
$count = count($file_paths);
|
|
|
|
$middle = \intdiv($count, $shuffle_count);
|
|
|
|
$remainder = $count % $shuffle_count;
|
2019-10-13 18:33:18 +02:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
$new_file_paths = [];
|
2019-10-13 18:33:18 +02:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
for ($i = 0; $i < $shuffle_count; $i++) {
|
|
|
|
for ($j = 0; $j < $middle; $j++) {
|
|
|
|
if ($j * $shuffle_count + $i < $count) {
|
|
|
|
$new_file_paths[] = $file_paths[$j * $shuffle_count + $i];
|
2019-10-13 18:33:18 +02:00
|
|
|
}
|
2019-10-14 02:09:50 +02:00
|
|
|
}
|
2019-10-13 18:33:18 +02:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
if ($remainder) {
|
|
|
|
$new_file_paths[] = $file_paths[$middle * $shuffle_count + $remainder - 1];
|
|
|
|
$remainder--;
|
2019-10-13 18:33:18 +02:00
|
|
|
}
|
2019-10-14 02:09:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$process_file_paths = [];
|
2019-10-13 18:33:18 +02:00
|
|
|
|
2019-10-14 02:09:50 +02:00
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($file_paths as $file_path) {
|
|
|
|
$process_file_paths[$i % $pool_size][] = $file_path;
|
|
|
|
++$i;
|
2019-10-13 18:33:18 +02:00
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
// Run analysis one file at a time, splitting the set of
|
|
|
|
// files up among a given number of child processes.
|
2018-11-06 03:57:36 +01:00
|
|
|
$pool = new \Psalm\Internal\Fork\Pool(
|
2018-02-04 00:52:35 +01:00
|
|
|
$process_file_paths,
|
|
|
|
/** @return void */
|
|
|
|
function () {
|
2019-04-17 00:10:26 +02:00
|
|
|
$project_analyzer = ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
|
|
|
|
|
|
|
$file_reference_provider = $codebase->file_reference_provider;
|
|
|
|
|
2020-05-22 22:55:36 +02:00
|
|
|
if ($codebase->taint) {
|
|
|
|
$codebase->taint = new \Psalm\Internal\Codebase\Taint();
|
|
|
|
}
|
2020-05-22 18:33:04 +02:00
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$file_reference_provider->setNonMethodReferencesToClasses([]);
|
2019-04-17 00:10:26 +02:00
|
|
|
$file_reference_provider->setCallingMethodReferencesToClassMembers([]);
|
|
|
|
$file_reference_provider->setFileReferencesToClassMembers([]);
|
|
|
|
$file_reference_provider->setCallingMethodReferencesToMissingClassMembers([]);
|
|
|
|
$file_reference_provider->setFileReferencesToMissingClassMembers([]);
|
2019-04-27 23:38:24 +02:00
|
|
|
$file_reference_provider->setReferencesToMixedMemberNames([]);
|
2019-04-30 21:35:57 +02:00
|
|
|
$file_reference_provider->setMethodParamUses([]);
|
2018-02-04 00:52:35 +01:00
|
|
|
},
|
|
|
|
$analysis_worker,
|
2018-09-26 22:33:59 +02:00
|
|
|
/** @return WorkerData */
|
2020-07-30 21:30:19 +02:00
|
|
|
function () {
|
2018-11-11 18:01:14 +01:00
|
|
|
$project_analyzer = ProjectAnalyzer::getInstance();
|
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2018-11-06 03:57:36 +01:00
|
|
|
$analyzer = $codebase->analyzer;
|
|
|
|
$file_reference_provider = $codebase->file_reference_provider;
|
2018-09-26 00:37:24 +02:00
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Gathering data for forked process' . "\n");
|
2019-04-17 00:10:26 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
// @codingStandardsIgnoreStart
|
2018-02-04 00:52:35 +01:00
|
|
|
return [
|
|
|
|
'issues' => IssueBuffer::getIssuesData(),
|
2019-12-02 21:24:01 +01:00
|
|
|
'fixable_issue_counts' => IssueBuffer::getFixableIssues(),
|
2020-07-30 21:30:19 +02:00
|
|
|
'nonmethod_references_to_classes' => $file_reference_provider->getAllNonMethodReferencesToClasses(),
|
|
|
|
'method_references_to_classes' => $file_reference_provider->getAllMethodReferencesToClasses(),
|
|
|
|
'file_references_to_class_members' => $file_reference_provider->getAllFileReferencesToClassMembers(),
|
|
|
|
'method_references_to_class_members' => $file_reference_provider->getAllMethodReferencesToClassMembers(),
|
|
|
|
'file_references_to_missing_class_members' => $file_reference_provider->getAllFileReferencesToMissingClassMembers(),
|
|
|
|
'method_references_to_missing_class_members' => $file_reference_provider->getAllMethodReferencesToMissingClassMembers(),
|
|
|
|
'method_param_uses' => $file_reference_provider->getAllMethodParamUses(),
|
|
|
|
'mixed_member_names' => $analyzer->getMixedMemberNames(),
|
|
|
|
'file_manipulations' => FileManipulationBuffer::getAll(),
|
|
|
|
'mixed_counts' => $analyzer->getMixedCounts(),
|
|
|
|
'function_timings' => $analyzer->getFunctionTimings(),
|
|
|
|
'analyzed_methods' => $analyzer->getAnalyzedMethods(),
|
|
|
|
'file_maps' => $analyzer->getFileMaps(),
|
|
|
|
'class_locations' => $file_reference_provider->getAllClassLocations(),
|
|
|
|
'class_method_locations' => $file_reference_provider->getAllClassMethodLocations(),
|
|
|
|
'class_property_locations' => $file_reference_provider->getAllClassPropertyLocations(),
|
|
|
|
'possible_method_param_types' => $analyzer->getPossibleMethodParamTypes(),
|
2019-08-04 16:37:36 +02:00
|
|
|
'taint_data' => $codebase->taint,
|
2019-08-18 20:27:50 +02:00
|
|
|
'unused_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUnusedSuppressions() : [],
|
2019-09-16 17:58:42 +02:00
|
|
|
'used_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUsedSuppressions() : [],
|
2020-08-25 01:24:27 +02:00
|
|
|
'function_docblock_manipulators' => FunctionDocblockManipulator::getManipulators(),
|
|
|
|
'mutable_classes' => $codebase->analyzer->mutable_classes,
|
2018-02-04 00:52:35 +01:00
|
|
|
];
|
2019-07-05 22:24:00 +02:00
|
|
|
// @codingStandardsIgnoreEnd
|
2019-05-30 16:30:41 +02:00
|
|
|
},
|
|
|
|
$task_done_closure
|
2018-02-04 00:52:35 +01:00
|
|
|
);
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Forking analysis' . "\n");
|
2019-04-17 00:10:26 +02:00
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
// Wait for all tasks to complete and collect the results.
|
|
|
|
/**
|
2018-09-26 22:33:59 +02:00
|
|
|
* @var array<int, WorkerData>
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
|
|
|
$forked_pool_data = $pool->wait();
|
|
|
|
|
2019-05-30 16:30:41 +02:00
|
|
|
$this->progress->debug('Collecting forked analysis results' . "\n");
|
2019-04-17 00:10:26 +02:00
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
foreach ($forked_pool_data as $pool_data) {
|
|
|
|
IssueBuffer::addIssues($pool_data['issues']);
|
2019-12-02 21:24:01 +01:00
|
|
|
IssueBuffer::addFixableIssues($pool_data['fixable_issue_counts']);
|
2018-09-26 22:33:59 +02:00
|
|
|
|
2019-08-18 20:27:50 +02:00
|
|
|
if ($codebase->track_unused_suppressions) {
|
|
|
|
IssueBuffer::addUnusedSuppressions($pool_data['unused_suppressions']);
|
2019-09-16 17:58:42 +02:00
|
|
|
IssueBuffer::addUsedSuppressions($pool_data['used_suppressions']);
|
2019-08-18 20:27:50 +02:00
|
|
|
}
|
|
|
|
|
2019-08-04 16:37:36 +02:00
|
|
|
if ($codebase->taint && $pool_data['taint_data']) {
|
|
|
|
$codebase->taint->addThreadData($pool_data['taint_data']);
|
|
|
|
}
|
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$codebase->file_reference_provider->addNonMethodReferencesToClasses(
|
|
|
|
$pool_data['nonmethod_references_to_classes']
|
2019-04-13 00:28:07 +02:00
|
|
|
);
|
2020-03-26 17:35:27 +01:00
|
|
|
$codebase->file_reference_provider->addMethodReferencesToClasses(
|
|
|
|
$pool_data['method_references_to_classes']
|
|
|
|
);
|
2019-04-16 22:07:48 +02:00
|
|
|
$codebase->file_reference_provider->addFileReferencesToClassMembers(
|
|
|
|
$pool_data['file_references_to_class_members']
|
|
|
|
);
|
|
|
|
$codebase->file_reference_provider->addMethodReferencesToClassMembers(
|
|
|
|
$pool_data['method_references_to_class_members']
|
|
|
|
);
|
|
|
|
$codebase->file_reference_provider->addFileReferencesToMissingClassMembers(
|
|
|
|
$pool_data['file_references_to_missing_class_members']
|
|
|
|
);
|
|
|
|
$codebase->file_reference_provider->addMethodReferencesToMissingClassMembers(
|
|
|
|
$pool_data['method_references_to_missing_class_members']
|
2019-04-13 00:28:07 +02:00
|
|
|
);
|
2019-04-29 23:29:38 +02:00
|
|
|
$codebase->file_reference_provider->addMethodParamUses(
|
|
|
|
$pool_data['method_param_uses']
|
|
|
|
);
|
2019-04-17 17:12:18 +02:00
|
|
|
$this->addMixedMemberNames(
|
|
|
|
$pool_data['mixed_member_names']
|
|
|
|
);
|
2020-07-30 21:30:19 +02:00
|
|
|
$this->function_timings += $pool_data['function_timings'];
|
2019-04-13 21:38:09 +02:00
|
|
|
$codebase->file_reference_provider->addClassLocations(
|
|
|
|
$pool_data['class_locations']
|
|
|
|
);
|
|
|
|
$codebase->file_reference_provider->addClassMethodLocations(
|
|
|
|
$pool_data['class_method_locations']
|
|
|
|
);
|
|
|
|
$codebase->file_reference_provider->addClassPropertyLocations(
|
|
|
|
$pool_data['class_property_locations']
|
|
|
|
);
|
|
|
|
|
2020-08-25 01:24:27 +02:00
|
|
|
$this->mutable_classes = array_merge($this->mutable_classes, $pool_data['mutable_classes']);
|
|
|
|
|
|
|
|
FunctionDocblockManipulator::addManipulators($pool_data['function_docblock_manipulators']);
|
2020-08-24 00:05:48 +02:00
|
|
|
|
2018-11-02 02:52:39 +01:00
|
|
|
$this->analyzed_methods = array_merge($pool_data['analyzed_methods'], $this->analyzed_methods);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($pool_data['mixed_counts'] as $file_path => [$mixed_count, $nonmixed_count]) {
|
2018-02-04 00:52:35 +01:00
|
|
|
if (!isset($this->mixed_counts[$file_path])) {
|
|
|
|
$this->mixed_counts[$file_path] = [$mixed_count, $nonmixed_count];
|
|
|
|
} else {
|
|
|
|
$this->mixed_counts[$file_path][0] += $mixed_count;
|
|
|
|
$this->mixed_counts[$file_path][1] += $nonmixed_count;
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 22:17:15 +02:00
|
|
|
|
2019-05-31 17:55:24 +02:00
|
|
|
foreach ($pool_data['possible_method_param_types'] as $declaring_method_id => $possible_param_types) {
|
|
|
|
if (!isset($this->possible_method_param_types[$declaring_method_id])) {
|
|
|
|
$this->possible_method_param_types[$declaring_method_id] = $possible_param_types;
|
|
|
|
} else {
|
|
|
|
foreach ($possible_param_types as $offset => $possible_param_type) {
|
|
|
|
if (!isset($this->possible_method_param_types[$declaring_method_id][$offset])) {
|
|
|
|
$this->possible_method_param_types[$declaring_method_id][$offset]
|
|
|
|
= $possible_param_type;
|
|
|
|
} else {
|
|
|
|
$this->possible_method_param_types[$declaring_method_id][$offset]
|
|
|
|
= \Psalm\Type::combineUnionTypes(
|
|
|
|
$this->possible_method_param_types[$declaring_method_id][$offset],
|
|
|
|
$possible_param_type,
|
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-17 19:15:06 +02:00
|
|
|
foreach ($pool_data['file_manipulations'] as $file_path => $manipulations) {
|
|
|
|
FileManipulationBuffer::add($file_path, $manipulations);
|
|
|
|
}
|
|
|
|
|
2019-07-01 21:54:33 +02:00
|
|
|
foreach ($pool_data['file_maps'] as $file_path => $file_maps) {
|
2020-09-02 06:17:41 +02:00
|
|
|
[$reference_map, $type_map, $argument_map] = $file_maps;
|
2018-10-26 22:17:15 +02:00
|
|
|
$this->reference_map[$file_path] = $reference_map;
|
|
|
|
$this->type_map[$file_path] = $type_map;
|
2019-07-01 21:54:33 +02:00
|
|
|
$this->argument_map[$file_path] = $argument_map;
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2018-10-15 23:40:42 +02:00
|
|
|
if ($pool->didHaveError()) {
|
|
|
|
exit(1);
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
} else {
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($this->files_to_analyze as $file_path => $_) {
|
|
|
|
$analysis_worker($i, $file_path);
|
|
|
|
++$i;
|
2019-05-30 16:30:41 +02:00
|
|
|
|
2020-01-22 03:07:44 +01:00
|
|
|
$issues = IssueBuffer::getIssuesDataForFile($file_path);
|
2019-05-30 16:30:41 +02:00
|
|
|
$task_done_closure($issues);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-11-11 18:01:14 +01:00
|
|
|
public function loadCachedResults(ProjectAnalyzer $project_analyzer)
|
2018-09-28 22:18:45 +02:00
|
|
|
{
|
2018-11-11 18:01:14 +01:00
|
|
|
$codebase = $project_analyzer->getCodebase();
|
2020-03-26 17:35:27 +01:00
|
|
|
|
2020-03-26 19:22:06 +01:00
|
|
|
if ($codebase->diff_methods) {
|
2018-11-11 18:19:53 +01:00
|
|
|
$this->analyzed_methods = $codebase->file_reference_provider->getAnalyzedMethods();
|
|
|
|
$this->existing_issues = $codebase->file_reference_provider->getExistingIssues();
|
|
|
|
$file_maps = $codebase->file_reference_provider->getFileMaps();
|
2018-10-26 22:17:15 +02:00
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($file_maps as $file_path => [$reference_map, $type_map, $argument_map]) {
|
2018-10-26 22:17:15 +02:00
|
|
|
$this->reference_map[$file_path] = $reference_map;
|
|
|
|
$this->type_map[$file_path] = $type_map;
|
2019-07-01 21:54:33 +02:00
|
|
|
$this->argument_map[$file_path] = $argument_map;
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$statements_provider = $codebase->statements_provider;
|
2019-04-16 22:07:48 +02:00
|
|
|
$file_reference_provider = $codebase->file_reference_provider;
|
2018-09-28 22:18:45 +02:00
|
|
|
|
|
|
|
$changed_members = $statements_provider->getChangedMembers();
|
2018-10-04 05:52:01 +02:00
|
|
|
$unchanged_signature_members = $statements_provider->getUnchangedSignatureMembers();
|
2018-09-28 22:18:45 +02:00
|
|
|
|
|
|
|
$diff_map = $statements_provider->getDiffMap();
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
$method_references_to_class_members
|
|
|
|
= $file_reference_provider->getAllMethodReferencesToClassMembers();
|
|
|
|
$method_references_to_missing_class_members =
|
|
|
|
$file_reference_provider->getAllMethodReferencesToMissingClassMembers();
|
|
|
|
|
|
|
|
$all_referencing_methods = $method_references_to_class_members + $method_references_to_missing_class_members;
|
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$nonmethod_references_to_classes = $file_reference_provider->getAllNonMethodReferencesToClasses();
|
2019-04-28 18:59:36 +02:00
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
$method_references_to_classes = $file_reference_provider->getAllMethodReferencesToClasses();
|
|
|
|
|
2019-04-29 23:29:38 +02:00
|
|
|
$method_param_uses = $file_reference_provider->getAllMethodParamUses();
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
$file_references_to_class_members
|
|
|
|
= $file_reference_provider->getAllFileReferencesToClassMembers();
|
|
|
|
$file_references_to_missing_class_members
|
|
|
|
= $file_reference_provider->getAllFileReferencesToMissingClassMembers();
|
2019-04-27 23:38:24 +02:00
|
|
|
|
|
|
|
$references_to_mixed_member_names = $file_reference_provider->getAllReferencesToMixedMemberNames();
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
$this->mixed_counts = $file_reference_provider->getTypeCoverage();
|
2018-09-28 22:18:45 +02:00
|
|
|
|
2020-03-02 21:26:57 +01:00
|
|
|
foreach ($changed_members as $file_path => $members_by_file) {
|
|
|
|
foreach ($members_by_file as $changed_member => $_) {
|
|
|
|
if (!strpos($changed_member, '&')) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
[$base_class, $trait] = explode('&', $changed_member);
|
2020-03-02 21:26:57 +01:00
|
|
|
|
2020-03-02 21:49:05 +01:00
|
|
|
foreach ($all_referencing_methods as $member_id => $_) {
|
2020-03-02 21:26:57 +01:00
|
|
|
if (strpos($member_id, $base_class . '::') !== 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-03-02 22:02:45 +01:00
|
|
|
$member_bit = substr($member_id, \strlen($base_class) + 2);
|
2018-10-03 23:11:08 +02:00
|
|
|
|
2020-03-02 21:26:57 +01:00
|
|
|
if (isset($all_referencing_methods[$trait . '::' . $member_bit])) {
|
|
|
|
$changed_members[$file_path][$member_id] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
$newly_invalidated_methods = [];
|
|
|
|
|
2018-10-04 05:52:01 +02:00
|
|
|
foreach ($unchanged_signature_members as $file_unchanged_signature_members) {
|
|
|
|
$newly_invalidated_methods = array_merge($newly_invalidated_methods, $file_unchanged_signature_members);
|
2018-10-07 04:58:21 +02:00
|
|
|
|
|
|
|
foreach ($file_unchanged_signature_members as $unchanged_signature_member_id => $_) {
|
|
|
|
// also check for things that might invalidate constructor property initialisation
|
|
|
|
if (isset($all_referencing_methods[$unchanged_signature_member_id])) {
|
|
|
|
foreach ($all_referencing_methods[$unchanged_signature_member_id] as $referencing_method_id => $_) {
|
|
|
|
if (substr($referencing_method_id, -13) === '::__construct') {
|
2020-03-02 17:20:52 +01:00
|
|
|
$referencing_base_classlike = explode('::', $referencing_method_id)[0];
|
|
|
|
$unchanged_signature_classlike = explode('::', $unchanged_signature_member_id)[0];
|
|
|
|
|
|
|
|
if ($referencing_base_classlike === $unchanged_signature_classlike) {
|
|
|
|
$newly_invalidated_methods[$referencing_method_id] = true;
|
|
|
|
} else {
|
2020-07-07 23:10:51 +02:00
|
|
|
try {
|
|
|
|
$referencing_storage = $codebase->classlike_storage_provider->get(
|
|
|
|
$referencing_base_classlike
|
|
|
|
);
|
|
|
|
} catch (InvalidArgumentException $_) {
|
|
|
|
// Workaround for #3671
|
|
|
|
$newly_invalidated_methods[$referencing_method_id] = true;
|
|
|
|
$referencing_storage = null;
|
|
|
|
}
|
2020-03-02 17:20:52 +01:00
|
|
|
|
|
|
|
if (isset($referencing_storage->used_traits[$unchanged_signature_classlike])
|
|
|
|
|| isset($referencing_storage->parent_classes[$unchanged_signature_classlike])
|
|
|
|
) {
|
|
|
|
$newly_invalidated_methods[$referencing_method_id] = true;
|
|
|
|
}
|
|
|
|
}
|
2018-10-07 04:58:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-04 05:52:01 +02:00
|
|
|
}
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
foreach ($changed_members as $file_changed_members) {
|
|
|
|
foreach ($file_changed_members as $member_id => $_) {
|
2018-10-04 00:16:33 +02:00
|
|
|
$newly_invalidated_methods[$member_id] = true;
|
|
|
|
|
2018-09-28 22:18:45 +02:00
|
|
|
if (isset($all_referencing_methods[$member_id])) {
|
2018-10-03 19:58:32 +02:00
|
|
|
$newly_invalidated_methods = array_merge(
|
|
|
|
$all_referencing_methods[$member_id],
|
|
|
|
$newly_invalidated_methods
|
|
|
|
);
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
2018-10-03 23:11:08 +02:00
|
|
|
|
2019-07-05 22:24:00 +02:00
|
|
|
unset(
|
|
|
|
$method_references_to_class_members[$member_id],
|
|
|
|
$file_references_to_class_members[$member_id],
|
|
|
|
$method_references_to_missing_class_members[$member_id],
|
|
|
|
$file_references_to_missing_class_members[$member_id],
|
|
|
|
$references_to_mixed_member_names[$member_id],
|
|
|
|
$method_param_uses[$member_id]
|
|
|
|
);
|
2019-04-16 22:07:48 +02:00
|
|
|
|
2018-10-03 23:11:08 +02:00
|
|
|
$member_stub = preg_replace('/::.*$/', '::*', $member_id);
|
|
|
|
|
|
|
|
if (isset($all_referencing_methods[$member_stub])) {
|
|
|
|
$newly_invalidated_methods = array_merge(
|
|
|
|
$all_referencing_methods[$member_stub],
|
|
|
|
$newly_invalidated_methods
|
|
|
|
);
|
|
|
|
}
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
foreach ($newly_invalidated_methods as $method_id => $_) {
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($method_references_to_class_members as $i => $_) {
|
|
|
|
unset($method_references_to_class_members[$i][$method_id]);
|
2019-04-16 22:07:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($method_references_to_classes as $i => $_) {
|
|
|
|
unset($method_references_to_classes[$i][$method_id]);
|
2020-03-26 17:35:27 +01:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($method_references_to_missing_class_members as $i => $_) {
|
|
|
|
unset($method_references_to_missing_class_members[$i][$method_id]);
|
2019-04-16 22:07:48 +02:00
|
|
|
}
|
2019-04-27 23:38:24 +02:00
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($references_to_mixed_member_names as $i => $_) {
|
|
|
|
unset($references_to_mixed_member_names[$i][$method_id]);
|
2019-04-27 23:38:24 +02:00
|
|
|
}
|
2019-04-29 23:29:38 +02:00
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($method_param_uses as $i => $_) {
|
|
|
|
foreach ($method_param_uses[$i] as $j => $_) {
|
|
|
|
unset($method_param_uses[$i][$j][$method_id]);
|
2019-04-29 23:29:38 +02:00
|
|
|
}
|
|
|
|
}
|
2019-04-16 22:07:48 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 02:52:39 +01:00
|
|
|
foreach ($this->analyzed_methods as $file_path => $analyzed_methods) {
|
|
|
|
foreach ($analyzed_methods as $correct_method_id => $_) {
|
2018-10-04 00:16:33 +02:00
|
|
|
$trait_safe_method_id = $correct_method_id;
|
2018-10-03 19:58:32 +02:00
|
|
|
|
2018-10-04 00:16:33 +02:00
|
|
|
$correct_method_ids = explode('&', $correct_method_id);
|
|
|
|
|
|
|
|
$correct_method_id = $correct_method_ids[0];
|
2018-09-28 22:18:45 +02:00
|
|
|
|
2018-10-04 00:16:33 +02:00
|
|
|
if (isset($newly_invalidated_methods[$correct_method_id])
|
|
|
|
|| (isset($correct_method_ids[1])
|
|
|
|
&& isset($newly_invalidated_methods[$correct_method_ids[1]]))
|
2018-09-28 22:18:45 +02:00
|
|
|
) {
|
2018-11-02 02:52:39 +01:00
|
|
|
unset($this->analyzed_methods[$file_path][$trait_safe_method_id]);
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-07 02:11:19 +02:00
|
|
|
$this->shiftFileOffsets($diff_map);
|
|
|
|
|
|
|
|
foreach ($this->files_to_analyze as $file_path) {
|
2019-04-16 22:07:48 +02:00
|
|
|
$file_reference_provider->clearExistingIssuesForFile($file_path);
|
|
|
|
$file_reference_provider->clearExistingFileMapsForFile($file_path);
|
|
|
|
|
2019-03-23 14:50:47 +01:00
|
|
|
$this->setMixedCountsForFile($file_path, [0, 0]);
|
2019-04-16 22:07:48 +02:00
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($file_references_to_class_members as $i => $_) {
|
|
|
|
unset($file_references_to_class_members[$i][$file_path]);
|
2019-04-16 22:07:48 +02:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($nonmethod_references_to_classes as $i => $_) {
|
|
|
|
unset($nonmethod_references_to_classes[$i][$file_path]);
|
2019-04-28 18:59:36 +02:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($references_to_mixed_member_names as $i => $_) {
|
|
|
|
unset($references_to_mixed_member_names[$i][$file_path]);
|
2019-04-27 23:38:24 +02:00
|
|
|
}
|
|
|
|
|
2020-04-05 03:48:14 +02:00
|
|
|
foreach ($file_references_to_missing_class_members as $i => $_) {
|
|
|
|
unset($file_references_to_missing_class_members[$i][$file_path]);
|
2019-04-16 22:07:48 +02:00
|
|
|
}
|
2018-10-07 02:11:19 +02:00
|
|
|
}
|
2019-04-16 22:07:48 +02:00
|
|
|
|
2020-03-31 22:56:22 +02:00
|
|
|
foreach ($this->existing_issues as $file_path => $issues) {
|
|
|
|
if (!isset($this->files_to_analyze[$file_path])) {
|
2020-03-31 15:56:27 +02:00
|
|
|
unset($this->existing_issues[$file_path]);
|
2020-03-31 22:56:22 +02:00
|
|
|
|
|
|
|
if ($this->file_provider->fileExists($file_path)) {
|
2020-03-31 22:57:08 +02:00
|
|
|
IssueBuffer::addIssues([$file_path => array_values($issues)]);
|
2020-03-31 22:56:22 +02:00
|
|
|
}
|
2020-03-26 19:22:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
$method_references_to_class_members = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$method_references_to_class_members
|
2019-04-16 22:07:48 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$method_references_to_missing_class_members = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$method_references_to_missing_class_members
|
2019-04-16 22:07:48 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$file_references_to_class_members = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$file_references_to_class_members
|
2019-04-16 22:07:48 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$file_references_to_missing_class_members = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$file_references_to_missing_class_members
|
2019-04-16 22:07:48 +02:00
|
|
|
);
|
|
|
|
|
2019-04-27 23:38:24 +02:00
|
|
|
$references_to_mixed_member_names = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$references_to_mixed_member_names
|
2019-04-27 23:38:24 +02:00
|
|
|
);
|
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$nonmethod_references_to_classes = array_filter(
|
|
|
|
$nonmethod_references_to_classes
|
2020-03-26 17:35:27 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
$method_references_to_classes = array_filter(
|
|
|
|
$method_references_to_classes
|
2019-04-28 18:59:36 +02:00
|
|
|
);
|
|
|
|
|
2019-04-29 23:29:38 +02:00
|
|
|
$method_param_uses = array_filter(
|
2020-03-26 17:35:27 +01:00
|
|
|
$method_param_uses
|
2019-04-29 23:29:38 +02:00
|
|
|
);
|
|
|
|
|
2019-04-16 22:07:48 +02:00
|
|
|
$file_reference_provider->setCallingMethodReferencesToClassMembers(
|
|
|
|
$method_references_to_class_members
|
|
|
|
);
|
|
|
|
|
|
|
|
$file_reference_provider->setFileReferencesToClassMembers(
|
|
|
|
$file_references_to_class_members
|
|
|
|
);
|
|
|
|
|
|
|
|
$file_reference_provider->setCallingMethodReferencesToMissingClassMembers(
|
|
|
|
$method_references_to_missing_class_members
|
|
|
|
);
|
|
|
|
|
|
|
|
$file_reference_provider->setFileReferencesToMissingClassMembers(
|
|
|
|
$file_references_to_missing_class_members
|
|
|
|
);
|
2019-04-27 23:38:24 +02:00
|
|
|
|
|
|
|
$file_reference_provider->setReferencesToMixedMemberNames(
|
|
|
|
$references_to_mixed_member_names
|
|
|
|
);
|
2019-04-28 18:59:36 +02:00
|
|
|
|
2020-03-26 17:35:27 +01:00
|
|
|
$file_reference_provider->setCallingMethodReferencesToClasses(
|
|
|
|
$method_references_to_classes
|
|
|
|
);
|
|
|
|
|
2020-04-02 23:17:55 +02:00
|
|
|
$file_reference_provider->setNonMethodReferencesToClasses(
|
|
|
|
$nonmethod_references_to_classes
|
2019-04-28 18:59:36 +02:00
|
|
|
);
|
2019-04-29 23:29:38 +02:00
|
|
|
|
|
|
|
$file_reference_provider->setMethodParamUses(
|
|
|
|
$method_param_uses
|
|
|
|
);
|
2018-10-07 02:11:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-02-08 00:47:50 +01:00
|
|
|
* @param array<string, array<int, array{int, int, int, int}>> $diff_map
|
2019-07-05 22:24:00 +02:00
|
|
|
*
|
2018-10-07 02:11:19 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function shiftFileOffsets(array $diff_map)
|
|
|
|
{
|
2018-09-28 22:18:45 +02:00
|
|
|
foreach ($this->existing_issues as $file_path => &$file_issues) {
|
2018-11-02 02:52:39 +01:00
|
|
|
if (!isset($this->analyzed_methods[$file_path])) {
|
2018-09-28 22:18:45 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-09-30 05:51:06 +02:00
|
|
|
$file_diff_map = $diff_map[$file_path] ?? [];
|
2018-09-28 22:18:45 +02:00
|
|
|
|
|
|
|
if (!$file_diff_map) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_diff_offset = $file_diff_map[0][0];
|
|
|
|
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
|
|
|
|
|
|
|
|
foreach ($file_issues as $i => &$issue_data) {
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue_data->to < $first_diff_offset || $issue_data->from > $last_diff_offset) {
|
2019-01-08 06:54:48 +01:00
|
|
|
unset($file_issues[$i]);
|
2018-09-28 22:18:45 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-02-07 20:06:37 +01:00
|
|
|
$matched = false;
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($file_diff_map as [$from, $to, $file_offset, $line_offset]) {
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue_data->from >= $from
|
|
|
|
&& $issue_data->from <= $to
|
2019-02-08 00:47:50 +01:00
|
|
|
&& !$matched
|
|
|
|
) {
|
2020-02-17 00:24:40 +01:00
|
|
|
$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;
|
2019-02-07 20:06:37 +01:00
|
|
|
$matched = true;
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
|
|
|
}
|
2019-02-07 20:06:37 +01:00
|
|
|
|
|
|
|
if (!$matched) {
|
|
|
|
unset($file_issues[$i]);
|
|
|
|
}
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->reference_map as $file_path => &$reference_map) {
|
2018-11-02 02:52:39 +01:00
|
|
|
if (!isset($this->analyzed_methods[$file_path])) {
|
2018-10-26 22:17:15 +02:00
|
|
|
unset($this->reference_map[$file_path]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_diff_map = $diff_map[$file_path] ?? [];
|
|
|
|
|
|
|
|
if (!$file_diff_map) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_diff_offset = $file_diff_map[0][0];
|
|
|
|
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($reference_map as $reference_from => [$reference_to, $tag]) {
|
2018-10-26 22:17:15 +02:00
|
|
|
if ($reference_to < $first_diff_offset || $reference_from > $last_diff_offset) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($file_diff_map as [$from, $to, $file_offset]) {
|
2018-10-26 22:17:15 +02:00
|
|
|
if ($reference_from >= $from && $reference_from <= $to) {
|
|
|
|
unset($reference_map[$reference_from]);
|
|
|
|
$reference_map[$reference_from += $file_offset] = [
|
|
|
|
$reference_to += $file_offset,
|
2019-07-05 22:24:00 +02:00
|
|
|
$tag,
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->type_map as $file_path => &$type_map) {
|
2018-11-02 02:52:39 +01:00
|
|
|
if (!isset($this->analyzed_methods[$file_path])) {
|
2018-10-26 22:17:15 +02:00
|
|
|
unset($this->type_map[$file_path]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_diff_map = $diff_map[$file_path] ?? [];
|
|
|
|
|
|
|
|
if (!$file_diff_map) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_diff_offset = $file_diff_map[0][0];
|
|
|
|
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
|
2018-09-28 22:18:45 +02:00
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($type_map as $type_from => [$type_to, $tag]) {
|
2018-10-26 22:17:15 +02:00
|
|
|
if ($type_to < $first_diff_offset || $type_from > $last_diff_offset) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($file_diff_map as [$from, $to, $file_offset]) {
|
2018-10-26 22:17:15 +02:00
|
|
|
if ($type_from >= $from && $type_from <= $to) {
|
|
|
|
unset($type_map[$type_from]);
|
|
|
|
$type_map[$type_from += $file_offset] = [
|
|
|
|
$type_to += $file_offset,
|
2019-07-05 22:24:00 +02:00
|
|
|
$tag,
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-01 21:54:33 +02:00
|
|
|
|
|
|
|
foreach ($this->argument_map as $file_path => &$argument_map) {
|
|
|
|
if (!isset($this->analyzed_methods[$file_path])) {
|
|
|
|
unset($this->argument_map[$file_path]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_diff_map = $diff_map[$file_path] ?? [];
|
|
|
|
|
|
|
|
if (!$file_diff_map) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_diff_offset = $file_diff_map[0][0];
|
|
|
|
$last_diff_offset = $file_diff_map[count($file_diff_map) - 1][1];
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($argument_map as $argument_from => [$argument_to, $method_id, $argument_number]) {
|
2019-07-01 21:54:33 +02:00
|
|
|
if ($argument_to < $first_diff_offset || $argument_from > $last_diff_offset) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
foreach ($file_diff_map as [$from, $to, $file_offset]) {
|
2019-07-01 21:54:33 +02:00
|
|
|
if ($argument_from >= $from && $argument_from <= $to) {
|
|
|
|
unset($argument_map[$argument_from]);
|
|
|
|
$argument_map[$argument_from += $file_offset] = [
|
|
|
|
$argument_to += $file_offset,
|
|
|
|
$method_id,
|
|
|
|
$argument_number,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-28 22:18:45 +02:00
|
|
|
}
|
|
|
|
|
2019-04-17 17:12:18 +02:00
|
|
|
/**
|
2019-04-27 23:38:24 +02:00
|
|
|
* @return array<string, array<string, bool>>
|
2019-04-17 17:12:18 +02:00
|
|
|
*/
|
|
|
|
public function getMixedMemberNames() : array
|
|
|
|
{
|
|
|
|
return $this->mixed_member_names;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-04-27 23:38:24 +02:00
|
|
|
public function addMixedMemberName(string $member_id, string $reference)
|
2019-04-17 17:12:18 +02:00
|
|
|
{
|
2019-04-27 23:38:24 +02:00
|
|
|
$this->mixed_member_names[$member_id][$reference] = true;
|
2019-04-17 17:12:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function hasMixedMemberName(string $member_id) : bool
|
|
|
|
{
|
|
|
|
return isset($this->mixed_member_names[$member_id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-06-16 15:12:32 +02:00
|
|
|
* @param array<string, array<string, bool>> $names
|
2019-07-05 22:24:00 +02:00
|
|
|
*
|
2019-04-17 17:12:18 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addMixedMemberNames(array $names)
|
|
|
|
{
|
2019-10-12 02:17:07 +02:00
|
|
|
foreach ($names as $key => $name) {
|
|
|
|
if (isset($this->mixed_member_names[$key])) {
|
|
|
|
$this->mixed_member_names[$key] = array_merge(
|
|
|
|
$this->mixed_member_names[$key],
|
|
|
|
$name
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$this->mixed_member_names[$key] = $name;
|
|
|
|
}
|
|
|
|
}
|
2019-04-17 17:12:18 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 23:26:07 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return array{0:int, 1:int}
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getMixedCountsForFile($file_path): array
|
2018-04-13 23:26:07 +02:00
|
|
|
{
|
|
|
|
if (!isset($this->mixed_counts[$file_path])) {
|
|
|
|
$this->mixed_counts[$file_path] = [0, 0];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->mixed_counts[$file_path];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param array{0:int, 1:int} $mixed_counts
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setMixedCountsForFile($file_path, array $mixed_counts)
|
|
|
|
{
|
|
|
|
$this->mixed_counts[$file_path] = $mixed_counts;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function incrementMixedCount($file_path)
|
|
|
|
{
|
|
|
|
if (!$this->count_mixed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->mixed_counts[$file_path])) {
|
|
|
|
$this->mixed_counts[$file_path] = [0, 0];
|
|
|
|
}
|
|
|
|
|
|
|
|
++$this->mixed_counts[$file_path][0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2020-05-02 20:55:21 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function decrementMixedCount($file_path)
|
|
|
|
{
|
|
|
|
if (!$this->count_mixed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->mixed_counts[$file_path])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
--$this->mixed_counts[$file_path][0];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2018-02-04 00:52:35 +01:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function incrementNonMixedCount($file_path)
|
|
|
|
{
|
|
|
|
if (!$this->count_mixed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->mixed_counts[$file_path])) {
|
|
|
|
$this->mixed_counts[$file_path] = [0, 0];
|
|
|
|
}
|
|
|
|
|
|
|
|
++$this->mixed_counts[$file_path][1];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return array<string, array{0: int, 1: int}>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getMixedCounts(): array
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2019-03-23 14:50:47 +01:00
|
|
|
$all_deep_scanned_files = [];
|
|
|
|
|
|
|
|
foreach ($this->files_to_analyze as $file_path => $_) {
|
|
|
|
$all_deep_scanned_files[$file_path] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_intersect_key($this->mixed_counts, $all_deep_scanned_files);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2020-07-30 21:30:19 +02:00
|
|
|
/**
|
|
|
|
* @return array<string, float>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFunctionTimings(): array
|
2020-07-30 21:30:19 +02:00
|
|
|
{
|
|
|
|
return $this->function_timings;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function addFunctionTiming(string $function_id, float $time_per_node) : void
|
|
|
|
{
|
|
|
|
$this->function_timings[$function_id] = $time_per_node;
|
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-05-17 18:38:29 +02:00
|
|
|
public function addNodeType(
|
|
|
|
string $file_path,
|
|
|
|
PhpParser\Node $node,
|
|
|
|
string $node_type,
|
|
|
|
PhpParser\Node $parent_node = null
|
|
|
|
) {
|
2020-05-15 16:18:05 +02:00
|
|
|
if (!$node_type) {
|
|
|
|
throw new \UnexpectedValueException('non-empty node_type expected');
|
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
$this->type_map[$file_path][(int)$node->getAttribute('startFilePos')] = [
|
2019-05-17 18:38:29 +02:00
|
|
|
($parent_node ? (int)$parent_node->getAttribute('endFilePos') : (int)$node->getAttribute('endFilePos')) + 1,
|
2019-07-05 22:24:00 +02:00
|
|
|
$node_type,
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-07-01 21:54:33 +02:00
|
|
|
public function addNodeArgument(
|
|
|
|
string $file_path,
|
|
|
|
int $start_position,
|
|
|
|
int $end_position,
|
|
|
|
string $reference,
|
|
|
|
int $argument_number
|
|
|
|
): void {
|
2020-05-15 16:18:05 +02:00
|
|
|
if (!$reference) {
|
|
|
|
throw new \UnexpectedValueException('non-empty node_type expected');
|
|
|
|
}
|
|
|
|
|
2019-07-01 21:54:33 +02:00
|
|
|
$this->argument_map[$file_path][$start_position] = [
|
|
|
|
$end_position,
|
|
|
|
$reference,
|
2019-07-05 22:24:00 +02:00
|
|
|
$argument_number,
|
2019-07-01 21:54:33 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addNodeReference(string $file_path, PhpParser\Node $node, string $reference)
|
|
|
|
{
|
2020-05-15 16:18:05 +02:00
|
|
|
if (!$reference) {
|
|
|
|
throw new \UnexpectedValueException('non-empty node_type expected');
|
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
$this->reference_map[$file_path][(int)$node->getAttribute('startFilePos')] = [
|
2019-02-24 07:33:25 +01:00
|
|
|
(int)$node->getAttribute('endFilePos') + 1,
|
2019-07-05 22:24:00 +02:00
|
|
|
$reference,
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function addOffsetReference(string $file_path, int $start, int $end, string $reference)
|
|
|
|
{
|
2020-05-15 16:18:05 +02:00
|
|
|
if (!$reference) {
|
|
|
|
throw new \UnexpectedValueException('non-empty node_type expected');
|
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
$this->reference_map[$file_path][$start] = [
|
|
|
|
$end,
|
2019-07-05 22:24:00 +02:00
|
|
|
$reference,
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
/**
|
2019-03-23 14:50:47 +01:00
|
|
|
* @return array{int, int}
|
2018-02-04 00:52:35 +01:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getTotalTypeCoverage(\Psalm\Codebase $codebase): array
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$mixed_count = 0;
|
|
|
|
$nonmixed_count = 0;
|
|
|
|
|
2019-03-23 14:50:47 +01:00
|
|
|
foreach ($codebase->file_reference_provider->getTypeCoverage() as $file_path => $counts) {
|
|
|
|
if (!$this->config->reportTypeStatsForFile($file_path)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
[$path_mixed_count, $path_nonmixed_count] = $counts;
|
2019-03-23 14:50:47 +01:00
|
|
|
|
|
|
|
if (isset($this->mixed_counts[$file_path])) {
|
|
|
|
$mixed_count += $path_mixed_count;
|
|
|
|
$nonmixed_count += $path_nonmixed_count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return [$mixed_count, $nonmixed_count];
|
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getTypeInferenceSummary(\Psalm\Codebase $codebase): string
|
2019-03-23 14:50:47 +01:00
|
|
|
{
|
2018-06-04 00:31:43 +02:00
|
|
|
$all_deep_scanned_files = [];
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
foreach ($this->files_to_analyze as $file_path => $_) {
|
2018-06-04 00:31:43 +02:00
|
|
|
$all_deep_scanned_files[$file_path] = true;
|
|
|
|
|
|
|
|
foreach ($this->file_storage_provider->get($file_path)->required_file_paths as $required_file_path) {
|
|
|
|
$all_deep_scanned_files[$required_file_path] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-02 06:17:41 +02:00
|
|
|
[$mixed_count, $nonmixed_count] = $this->getTotalTypeCoverage($codebase);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
|
|
|
$total = $mixed_count + $nonmixed_count;
|
|
|
|
|
2018-06-04 00:31:43 +02:00
|
|
|
$total_files = count($all_deep_scanned_files);
|
|
|
|
|
|
|
|
if (!$total_files) {
|
|
|
|
return 'No files analyzed';
|
|
|
|
}
|
|
|
|
|
2018-06-27 19:41:50 +02:00
|
|
|
if (!$total) {
|
2019-03-23 14:50:47 +01:00
|
|
|
return 'Psalm was unable to infer types in the codebase';
|
2018-06-27 19:41:50 +02:00
|
|
|
}
|
|
|
|
|
2019-03-23 14:50:47 +01:00
|
|
|
$percentage = $nonmixed_count === $total ? '100' : number_format(100 * $nonmixed_count / $total, 4);
|
|
|
|
|
|
|
|
return 'Psalm was able to infer types for ' . $percentage . '%'
|
|
|
|
. ' of the codebase';
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getNonMixedStats(): string
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
|
|
|
$stats = '';
|
|
|
|
|
2018-06-04 00:31:43 +02:00
|
|
|
$all_deep_scanned_files = [];
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
foreach ($this->files_to_analyze as $file_path => $_) {
|
2018-06-04 00:31:43 +02:00
|
|
|
$all_deep_scanned_files[$file_path] = true;
|
|
|
|
|
2018-08-24 23:46:13 +02:00
|
|
|
if (!$this->config->reportTypeStatsForFile($file_path)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-06-04 00:31:43 +02:00
|
|
|
foreach ($this->file_storage_provider->get($file_path)->required_file_paths as $required_file_path) {
|
|
|
|
$all_deep_scanned_files[$required_file_path] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($all_deep_scanned_files as $file_path => $_) {
|
2018-02-04 00:52:35 +01:00
|
|
|
if (isset($this->mixed_counts[$file_path])) {
|
2020-09-02 06:17:41 +02:00
|
|
|
[$path_mixed_count, $path_nonmixed_count] = $this->mixed_counts[$file_path];
|
2019-04-13 19:41:39 +02:00
|
|
|
|
|
|
|
if ($path_mixed_count + $path_nonmixed_count) {
|
|
|
|
$stats .= number_format(100 * $path_nonmixed_count / ($path_mixed_count + $path_nonmixed_count), 0)
|
|
|
|
. '% ' . $this->config->shortenFileName($file_path)
|
|
|
|
. ' (' . $path_mixed_count . ' mixed)' . "\n";
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function disableMixedCounts()
|
|
|
|
{
|
|
|
|
$this->count_mixed = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function enableMixedCounts()
|
|
|
|
{
|
|
|
|
$this->count_mixed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param bool $dry_run
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-06-02 15:59:45 +02:00
|
|
|
public function updateFile($file_path, $dry_run)
|
2018-02-04 00:52:35 +01:00
|
|
|
{
|
2019-08-09 04:58:54 +02:00
|
|
|
FileManipulationBuffer::add(
|
|
|
|
$file_path,
|
|
|
|
FunctionDocblockManipulator::getManipulationsForFile($file_path)
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2020-07-08 18:03:12 +02:00
|
|
|
FileManipulationBuffer::add(
|
|
|
|
$file_path,
|
|
|
|
PropertyDocblockManipulator::getManipulationsForFile($file_path)
|
|
|
|
);
|
|
|
|
|
2020-08-25 01:24:27 +02:00
|
|
|
FileManipulationBuffer::add(
|
|
|
|
$file_path,
|
|
|
|
ClassDocblockManipulator::getManipulationsForFile($file_path)
|
|
|
|
);
|
|
|
|
|
2019-08-09 04:58:54 +02:00
|
|
|
$file_manipulations = FileManipulationBuffer::getManipulationsForFile($file_path);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
if (!$file_manipulations) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-04 00:52:35 +01:00
|
|
|
usort(
|
|
|
|
$file_manipulations,
|
2020-09-04 22:26:33 +02:00
|
|
|
function (FileManipulation $a, FileManipulation $b): int {
|
2019-08-09 04:58:54 +02:00
|
|
|
if ($b->end === $a->end) {
|
|
|
|
if ($a->start === $b->start) {
|
2018-02-04 00:52:35 +01:00
|
|
|
return $b->insertion_text > $a->insertion_text ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
2019-08-09 04:58:54 +02:00
|
|
|
return $b->start > $a->start ? 1 : -1;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-08-09 04:58:54 +02:00
|
|
|
return $b->end > $a->end ? 1 : -1;
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2019-08-09 04:58:54 +02:00
|
|
|
$last_start = \PHP_INT_MAX;
|
2018-02-04 00:52:35 +01:00
|
|
|
$existing_contents = $this->file_provider->getContents($file_path);
|
|
|
|
|
|
|
|
foreach ($file_manipulations as $manipulation) {
|
2019-08-09 04:58:54 +02:00
|
|
|
if ($manipulation->start <= $last_start) {
|
|
|
|
$existing_contents = $manipulation->transform($existing_contents);
|
|
|
|
$last_start = $manipulation->start;
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
if ($dry_run) {
|
|
|
|
echo $file_path . ':' . "\n";
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
$differ = new \SebastianBergmann\Diff\Differ(
|
|
|
|
new \SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder([
|
|
|
|
'fromFile' => $file_path,
|
|
|
|
'toFile' => $file_path,
|
|
|
|
])
|
|
|
|
);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-07-19 05:37:36 +02:00
|
|
|
echo $differ->diff($this->file_provider->getContents($file_path), $existing_contents);
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-06-01 06:56:54 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
|
2019-06-02 15:59:45 +02:00
|
|
|
$this->progress->alterFileDone($file_path);
|
2019-06-01 06:56:54 +02:00
|
|
|
|
|
|
|
$this->file_provider->setContents($file_path, $existing_contents);
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|
2018-09-26 00:37:24 +02:00
|
|
|
|
2018-09-26 22:33:59 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param int $start
|
|
|
|
* @param int $end
|
|
|
|
*
|
2020-01-22 03:07:44 +01:00
|
|
|
* @return list<IssueData>
|
2018-09-26 22:33:59 +02:00
|
|
|
*/
|
2019-06-13 21:25:55 +02:00
|
|
|
public function getExistingIssuesForFile($file_path, $start, $end, ?string $issue_type = null)
|
2018-09-26 22:33:59 +02:00
|
|
|
{
|
|
|
|
if (!isset($this->existing_issues[$file_path])) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
$applicable_issues = [];
|
|
|
|
|
|
|
|
foreach ($this->existing_issues[$file_path] as $issue_data) {
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue_data->from >= $start && $issue_data->from <= $end) {
|
|
|
|
if ($issue_type === null || $issue_type === $issue_data->type) {
|
2019-06-13 21:25:55 +02:00
|
|
|
$applicable_issues[] = $issue_data;
|
|
|
|
}
|
2018-09-26 22:33:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $applicable_issues;
|
|
|
|
}
|
|
|
|
|
2018-10-26 06:59:14 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param int $start
|
|
|
|
* @param int $end
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-02-03 22:43:17 +01:00
|
|
|
public function removeExistingDataForFile($file_path, $start, $end, ?string $issue_type = null)
|
2018-10-26 06:59:14 +02:00
|
|
|
{
|
|
|
|
if (isset($this->existing_issues[$file_path])) {
|
|
|
|
foreach ($this->existing_issues[$file_path] as $i => $issue_data) {
|
2020-02-17 00:24:40 +01:00
|
|
|
if ($issue_data->from >= $start && $issue_data->from <= $end) {
|
|
|
|
if ($issue_type === null || $issue_type === $issue_data->type) {
|
2020-02-03 22:43:17 +01:00
|
|
|
unset($this->existing_issues[$file_path][$i]);
|
|
|
|
}
|
2018-10-26 06:59:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 22:17:15 +02:00
|
|
|
|
|
|
|
if (isset($this->type_map[$file_path])) {
|
|
|
|
foreach ($this->type_map[$file_path] as $map_start => $_) {
|
|
|
|
if ($map_start >= $start && $map_start <= $end) {
|
|
|
|
unset($this->type_map[$file_path][$map_start]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->reference_map[$file_path])) {
|
|
|
|
foreach ($this->reference_map[$file_path] as $map_start => $_) {
|
|
|
|
if ($map_start >= $start && $map_start <= $end) {
|
|
|
|
unset($this->reference_map[$file_path][$map_start]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-01 21:54:33 +02:00
|
|
|
|
|
|
|
if (isset($this->argument_map[$file_path])) {
|
|
|
|
foreach ($this->argument_map[$file_path] as $map_start => $_) {
|
|
|
|
if ($map_start >= $start && $map_start <= $end) {
|
|
|
|
unset($this->argument_map[$file_path][$map_start]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-26 06:59:14 +02:00
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
2018-10-07 04:58:21 +02:00
|
|
|
* @return array<string, array<string, int>>
|
2018-09-26 00:37:24 +02:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getAnalyzedMethods(): array
|
2018-09-26 00:37:24 +02:00
|
|
|
{
|
2018-11-02 02:52:39 +01:00
|
|
|
return $this->analyzed_methods;
|
2018-09-26 00:37:24 +02:00
|
|
|
}
|
|
|
|
|
2018-10-26 22:17:15 +02:00
|
|
|
/**
|
2020-05-15 06:16:20 +02:00
|
|
|
* @return array<string, FileMapType>
|
2018-10-26 22:17:15 +02:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getFileMaps(): array
|
2018-10-26 22:17:15 +02:00
|
|
|
{
|
|
|
|
$file_maps = [];
|
|
|
|
|
|
|
|
foreach ($this->reference_map as $file_path => $reference_map) {
|
2019-07-01 21:54:33 +02:00
|
|
|
$file_maps[$file_path] = [$reference_map, [], []];
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->type_map as $file_path => $type_map) {
|
|
|
|
if (isset($file_maps[$file_path])) {
|
|
|
|
$file_maps[$file_path][1] = $type_map;
|
|
|
|
} else {
|
2019-07-01 21:54:33 +02:00
|
|
|
$file_maps[$file_path] = [[], $type_map, []];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->argument_map as $file_path => $argument_map) {
|
|
|
|
if (isset($file_maps[$file_path])) {
|
|
|
|
$file_maps[$file_path][2] = $argument_map;
|
|
|
|
} else {
|
|
|
|
$file_maps[$file_path] = [[], [], $argument_map];
|
2018-10-26 22:17:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $file_maps;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-05-15 06:16:20 +02:00
|
|
|
* @return FileMapType
|
2018-10-26 22:17:15 +02:00
|
|
|
*/
|
|
|
|
public function getMapsForFile(string $file_path)
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
$this->reference_map[$file_path] ?? [],
|
2019-07-01 21:54:33 +02:00
|
|
|
$this->type_map[$file_path] ?? [],
|
|
|
|
$this->argument_map[$file_path] ?? [],
|
2018-10-26 22:17:15 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2019-05-31 17:55:24 +02:00
|
|
|
/**
|
|
|
|
* @return array<string, array<int, \Psalm\Type\Union>>
|
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function getPossibleMethodParamTypes(): array
|
2019-05-31 17:55:24 +02:00
|
|
|
{
|
|
|
|
return $this->possible_method_param_types;
|
|
|
|
}
|
|
|
|
|
2020-08-25 01:24:27 +02:00
|
|
|
public function addMutableClass(string $fqcln) : void
|
|
|
|
{
|
|
|
|
$this->mutable_classes[\strtolower($fqcln)] = true;
|
|
|
|
}
|
|
|
|
|
2018-09-26 00:37:24 +02:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param string $method_id
|
2018-10-07 04:58:21 +02:00
|
|
|
* @param bool $is_constructor
|
2018-09-26 00:37:24 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2018-11-02 02:52:39 +01:00
|
|
|
public function setAnalyzedMethod($file_path, $method_id, $is_constructor = false)
|
2018-09-26 00:37:24 +02:00
|
|
|
{
|
2018-11-02 02:52:39 +01:00
|
|
|
$this->analyzed_methods[$file_path][$method_id] = $is_constructor ? 2 : 1;
|
2018-09-26 00:37:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param string $method_id
|
2018-10-07 04:58:21 +02:00
|
|
|
* @param bool $is_constructor
|
2018-09-26 00:37:24 +02:00
|
|
|
*/
|
2020-09-04 22:26:33 +02:00
|
|
|
public function isMethodAlreadyAnalyzed($file_path, $method_id, $is_constructor = false): bool
|
2018-09-26 00:37:24 +02:00
|
|
|
{
|
2018-10-07 04:58:21 +02:00
|
|
|
if ($is_constructor) {
|
2018-11-02 02:52:39 +01:00
|
|
|
return isset($this->analyzed_methods[$file_path][$method_id])
|
|
|
|
&& $this->analyzed_methods[$file_path][$method_id] === 2;
|
2018-10-07 04:58:21 +02:00
|
|
|
}
|
|
|
|
|
2018-11-02 02:52:39 +01:00
|
|
|
return isset($this->analyzed_methods[$file_path][$method_id]);
|
2018-09-26 00:37:24 +02:00
|
|
|
}
|
2018-02-04 00:52:35 +01:00
|
|
|
}
|