2016-06-10 00:08:25 +02:00
|
|
|
<?php
|
2016-08-13 20:20:46 +02:00
|
|
|
namespace Psalm\Checker;
|
2016-06-10 00:08:25 +02:00
|
|
|
|
2016-08-13 20:20:46 +02:00
|
|
|
use Psalm\Config;
|
2017-01-12 06:54:41 +01:00
|
|
|
use Psalm\Context;
|
2016-08-14 01:44:24 +02:00
|
|
|
use Psalm\Exception;
|
2017-07-25 22:11:02 +02:00
|
|
|
use Psalm\Exception\CircularReferenceException;
|
2017-09-16 18:45:11 +02:00
|
|
|
use Psalm\FileManipulation\FunctionDocblockManipulator;
|
2017-02-24 01:52:23 +01:00
|
|
|
use Psalm\Issue\PossiblyUnusedMethod;
|
2017-02-08 06:28:26 +01:00
|
|
|
use Psalm\Issue\UnusedClass;
|
|
|
|
use Psalm\Issue\UnusedMethod;
|
2017-05-19 06:48:26 +02:00
|
|
|
use Psalm\IssueBuffer;
|
2017-07-29 21:05:06 +02:00
|
|
|
use Psalm\Provider\ClassLikeStorageProvider;
|
2017-02-18 19:41:27 +01:00
|
|
|
use Psalm\Provider\FileProvider;
|
|
|
|
use Psalm\Provider\FileReferenceProvider;
|
2017-07-29 21:05:06 +02:00
|
|
|
use Psalm\Provider\FileStorageProvider;
|
2017-10-15 17:57:44 +02:00
|
|
|
use Psalm\Provider\ParserCacheProvider;
|
2017-07-25 22:11:02 +02:00
|
|
|
use Psalm\Provider\StatementsProvider;
|
|
|
|
use Psalm\Storage\ClassLikeStorage;
|
|
|
|
use Psalm\Storage\FileStorage;
|
2017-01-02 21:31:18 +01:00
|
|
|
use Psalm\Type;
|
2016-11-02 07:29:00 +01:00
|
|
|
use RecursiveDirectoryIterator;
|
|
|
|
use RecursiveIteratorIterator;
|
2016-08-13 20:20:46 +02:00
|
|
|
|
2016-06-10 00:08:25 +02:00
|
|
|
class ProjectChecker
|
|
|
|
{
|
2016-06-26 19:45:20 +02:00
|
|
|
/**
|
|
|
|
* Cached config
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2016-06-26 19:45:20 +02:00
|
|
|
* @var Config|null
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $config;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var self
|
|
|
|
*/
|
|
|
|
public static $instance;
|
2016-06-26 19:45:20 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/** @var FileProvider */
|
|
|
|
private $file_provider;
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
/** @var FileStorageProvider */
|
|
|
|
public $file_storage_provider;
|
|
|
|
|
|
|
|
/** @var ClassLikeStorageProvider */
|
|
|
|
public $classlike_storage_provider;
|
|
|
|
|
2017-10-15 17:57:44 +02:00
|
|
|
/** @var ParserCacheProvider */
|
2017-07-25 22:11:02 +02:00
|
|
|
public $cache_provider;
|
|
|
|
|
2016-07-25 21:05:58 +02:00
|
|
|
/**
|
|
|
|
* Whether or not to use colors in error output
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2016-07-25 21:05:58 +02:00
|
|
|
*/
|
2016-12-08 04:38:57 +01:00
|
|
|
public $use_color;
|
2016-07-25 21:05:58 +02:00
|
|
|
|
2016-08-04 20:38:43 +02:00
|
|
|
/**
|
|
|
|
* Whether or not to show informational messages
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2016-08-04 20:38:43 +02:00
|
|
|
*/
|
2016-12-08 04:38:57 +01:00
|
|
|
public $show_info;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
public $output_format;
|
|
|
|
|
2017-01-30 04:30:27 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
2017-02-27 05:09:18 +01:00
|
|
|
public $collect_references = false;
|
2017-01-30 04:30:27 +01:00
|
|
|
|
2017-02-27 07:30:44 +01:00
|
|
|
/**
|
|
|
|
* @var string|null
|
|
|
|
*/
|
|
|
|
public $find_references_to;
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $debug_output = false;
|
|
|
|
|
2017-01-13 20:06:05 +01:00
|
|
|
/**
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2017-01-13 20:06:05 +01:00
|
|
|
*/
|
|
|
|
public $update_docblocks = false;
|
|
|
|
|
2017-01-12 06:54:41 +01:00
|
|
|
/**
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2017-01-12 06:54:41 +01:00
|
|
|
*/
|
|
|
|
public $cache = false;
|
|
|
|
|
2017-01-09 05:58:06 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $existing_classlikes_lc = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $existing_classlikes = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $existing_classes_lc = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $reflected_classeslikes_lc = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
public $existing_classes = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $existing_interfaces_lc = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
public $existing_interfaces = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $existing_traits_lc = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
public $existing_traits = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $classlike_files = [];
|
2017-01-09 05:58:06 +01:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $files_to_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $classes_to_scan = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-01-08 18:55:32 +01:00
|
|
|
/**
|
2017-03-24 23:34:46 +01:00
|
|
|
* @var array<string, bool>
|
2017-01-08 18:55:32 +01:00
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $classes_to_deep_scan = [];
|
|
|
|
|
2017-07-27 22:59:41 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
|
|
|
private $store_scan_failure = [];
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/**
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_to_deep_scan = [];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We analyze more files than we necessarily report errors in
|
|
|
|
*
|
|
|
|
* @var array<string, string>
|
|
|
|
*/
|
|
|
|
private $files_to_report = [];
|
2017-01-08 18:55:32 +01:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private $scanned_files = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, bool>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $visited_classes = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array<string, FileChecker>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private $file_checkers = [];
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-01-12 06:54:41 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, MethodChecker>
|
|
|
|
*/
|
|
|
|
public $method_checkers = [];
|
|
|
|
|
2017-01-30 05:44:05 +01:00
|
|
|
/**
|
|
|
|
* @var array<string, int>
|
|
|
|
*/
|
|
|
|
public $classlike_references = [];
|
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
/**
|
|
|
|
* A map of fully-qualified use declarations to the files
|
|
|
|
* that reference them (keyed by filename)
|
2017-03-11 01:36:17 +01:00
|
|
|
*
|
2017-02-28 06:29:28 +01:00
|
|
|
* @var array<string, array<string, array<int, \Psalm\CodeLocation>>>
|
2017-02-28 00:24:20 +01:00
|
|
|
*/
|
|
|
|
public $use_referencing_locations = [];
|
|
|
|
|
2017-03-11 01:36:17 +01:00
|
|
|
/**
|
|
|
|
* A map of file names to the classes that they contain explicit references to
|
|
|
|
* used in collaboration with use_referencing_locations
|
|
|
|
*
|
|
|
|
* @var array<string, array<string, bool>>
|
|
|
|
*/
|
|
|
|
public $use_referencing_files = [];
|
|
|
|
|
2017-02-15 06:15:51 +01:00
|
|
|
/**
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2017-02-15 06:15:51 +01:00
|
|
|
*/
|
|
|
|
public $server_mode = false;
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/** @var int */
|
|
|
|
public $threads;
|
|
|
|
|
2017-09-03 00:15:52 +02:00
|
|
|
/**
|
|
|
|
* Whether or not to infer types from usage. Computationally expensive, so turned off by default
|
|
|
|
*
|
|
|
|
* @var bool
|
|
|
|
*/
|
|
|
|
public $infer_types_from_usage = false;
|
|
|
|
|
2017-09-08 17:18:48 +02:00
|
|
|
/**
|
|
|
|
* @var array<string,string>
|
|
|
|
*/
|
|
|
|
public $reports = [];
|
|
|
|
|
2017-02-11 00:12:59 +01:00
|
|
|
/**
|
|
|
|
* Whether to log functions just at the file level or globally (for stubs)
|
|
|
|
*
|
2017-05-25 04:07:49 +02:00
|
|
|
* @var bool
|
2017-02-11 00:12:59 +01:00
|
|
|
*/
|
|
|
|
public $register_global_functions = false;
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
const TYPE_CONSOLE = 'console';
|
|
|
|
const TYPE_JSON = 'json';
|
2017-01-16 04:39:26 +01:00
|
|
|
const TYPE_EMACS = 'emacs';
|
2017-09-08 17:18:48 +02:00
|
|
|
const TYPE_XML = 'xml';
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2016-12-17 06:48:31 +01:00
|
|
|
/**
|
2017-09-08 17:18:48 +02:00
|
|
|
* @param FileProvider $file_provider
|
2017-10-15 17:57:44 +02:00
|
|
|
* @param ParserCacheProvider $cache_provider
|
2017-09-08 17:18:48 +02:00
|
|
|
* @param bool $use_color
|
|
|
|
* @param bool $show_info
|
|
|
|
* @param string $output_format
|
|
|
|
* @param int $threads
|
|
|
|
* @param bool $debug_output
|
|
|
|
* @param bool $update_docblocks
|
|
|
|
* @param bool $collect_references
|
|
|
|
* @param string $find_references_to
|
|
|
|
* @param string $reports
|
2016-12-17 06:48:31 +01:00
|
|
|
*/
|
2017-01-02 21:31:18 +01:00
|
|
|
public function __construct(
|
2017-07-25 22:11:02 +02:00
|
|
|
FileProvider $file_provider,
|
2017-10-15 17:57:44 +02:00
|
|
|
ParserCacheProvider $cache_provider,
|
2017-01-02 21:31:18 +01:00
|
|
|
$use_color = true,
|
|
|
|
$show_info = true,
|
|
|
|
$output_format = self::TYPE_CONSOLE,
|
2017-07-25 22:11:02 +02:00
|
|
|
$threads = 1,
|
2017-01-13 20:06:05 +01:00
|
|
|
$debug_output = false,
|
2017-02-01 02:47:16 +01:00
|
|
|
$update_docblocks = false,
|
2017-02-27 07:30:44 +01:00
|
|
|
$collect_references = false,
|
2017-09-08 17:18:48 +02:00
|
|
|
$find_references_to = null,
|
|
|
|
$reports = null
|
2017-01-02 21:31:18 +01:00
|
|
|
) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->file_provider = $file_provider;
|
|
|
|
$this->cache_provider = $cache_provider;
|
2016-12-08 04:38:57 +01:00
|
|
|
$this->use_color = $use_color;
|
|
|
|
$this->show_info = $show_info;
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->debug_output = $debug_output;
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->threads = $threads;
|
2017-01-13 20:06:05 +01:00
|
|
|
$this->update_docblocks = $update_docblocks;
|
2017-02-27 07:30:44 +01:00
|
|
|
$this->collect_references = $collect_references;
|
|
|
|
$this->find_references_to = $find_references_to;
|
2016-12-08 04:38:57 +01:00
|
|
|
|
2017-09-08 17:18:48 +02:00
|
|
|
if (!in_array($output_format, [self::TYPE_CONSOLE, self::TYPE_JSON, self::TYPE_EMACS, self::TYPE_XML], true)) {
|
2016-12-08 04:38:57 +01:00
|
|
|
throw new \UnexpectedValueException('Unrecognised output format ' . $output_format);
|
|
|
|
}
|
|
|
|
|
2017-09-08 17:18:48 +02:00
|
|
|
if ($reports) {
|
|
|
|
/**
|
|
|
|
* @var array<string,string>
|
|
|
|
*/
|
|
|
|
$mapping = [
|
|
|
|
'.xml' => self::TYPE_XML,
|
|
|
|
'.json' => self::TYPE_JSON,
|
|
|
|
'.txt' => self::TYPE_EMACS,
|
|
|
|
'.emacs' => self::TYPE_EMACS,
|
|
|
|
];
|
|
|
|
foreach ($mapping as $extension => $type) {
|
|
|
|
if (substr($reports, -strlen($extension)) === $extension) {
|
|
|
|
$this->reports[$type] = $reports;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($this->reports)) {
|
|
|
|
throw new \UnexpectedValueException('Unrecognised report format ' . $reports);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
$this->output_format = $output_format;
|
|
|
|
self::$instance = $this;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
$this->collectPredefinedClassLikes();
|
2017-07-29 21:05:06 +02:00
|
|
|
|
|
|
|
$this->file_storage_provider = new FileStorageProvider();
|
|
|
|
$this->classlike_storage_provider = new ClassLikeStorageProvider();
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return self
|
|
|
|
*/
|
|
|
|
public static function getInstance()
|
|
|
|
{
|
|
|
|
return self::$instance;
|
|
|
|
}
|
2016-08-04 20:38:43 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
2017-05-04 20:25:58 +02:00
|
|
|
* @param string $base_dir
|
2017-05-25 04:07:49 +02:00
|
|
|
* @param bool $is_diff
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-05-04 20:25:58 +02:00
|
|
|
public function check($base_dir, $is_diff = false)
|
2016-06-13 21:33:18 +02:00
|
|
|
{
|
2016-11-06 05:59:29 +01:00
|
|
|
$start_checks = (int)microtime(true);
|
|
|
|
|
2017-05-04 20:25:58 +02:00
|
|
|
if (!$base_dir) {
|
|
|
|
throw new \InvalidArgumentException('Cannot work with empty base_dir');
|
2016-10-15 06:12:57 +02:00
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
if (!$this->config) {
|
2017-05-04 20:25:58 +02:00
|
|
|
$this->config = $this->getConfigForPath($base_dir, $base_dir);
|
2016-06-26 21:33:51 +02:00
|
|
|
}
|
2016-06-26 19:45:20 +02:00
|
|
|
|
2016-10-07 06:58:08 +02:00
|
|
|
$diff_files = null;
|
2016-10-07 19:26:29 +02:00
|
|
|
$deleted_files = null;
|
2016-10-05 19:24:46 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($is_diff && FileReferenceProvider::loadReferenceCache() && $this->cache_provider->canDiffFiles()) {
|
2017-02-18 19:41:27 +01:00
|
|
|
$deleted_files = FileReferenceProvider::getDeletedReferencedFiles();
|
2016-10-07 19:26:29 +02:00
|
|
|
$diff_files = $deleted_files;
|
2016-10-07 06:58:08 +02:00
|
|
|
|
2016-12-29 16:24:10 +01:00
|
|
|
foreach ($this->config->getProjectDirectories() as $dir_name) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$diff_files = array_merge($diff_files, $this->getDiffFilesInDir($dir_name, $this->config));
|
2016-10-07 06:58:08 +02:00
|
|
|
}
|
2016-10-05 19:24:46 +02:00
|
|
|
}
|
2016-10-07 06:58:08 +02:00
|
|
|
|
2016-10-07 19:26:29 +02:00
|
|
|
if ($diff_files === null || $deleted_files === null || count($diff_files) > 200) {
|
2016-12-29 16:24:10 +01:00
|
|
|
foreach ($this->config->getProjectDirectories() as $dir_name) {
|
2017-01-13 20:06:05 +01:00
|
|
|
$this->checkDirWithConfig($dir_name, $this->config);
|
2016-10-05 19:24:46 +02:00
|
|
|
}
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->scanFiles();
|
2017-02-15 06:15:51 +01:00
|
|
|
|
|
|
|
if (!$this->server_mode) {
|
|
|
|
$this->analyzeFiles();
|
|
|
|
}
|
2016-11-02 07:29:00 +01:00
|
|
|
} else {
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($this->debug_output) {
|
2016-10-07 06:58:08 +02:00
|
|
|
echo count($diff_files) . ' changed files' . PHP_EOL;
|
|
|
|
}
|
2016-06-21 01:30:38 +02:00
|
|
|
|
2016-10-07 06:58:08 +02:00
|
|
|
$file_list = self::getReferencedFilesFromDiff($diff_files);
|
2016-11-02 07:29:00 +01:00
|
|
|
|
2016-10-07 19:26:29 +02:00
|
|
|
// strip out deleted files
|
|
|
|
$file_list = array_diff($file_list, $deleted_files);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->checkDiffFilesWithConfig($this->config, $file_list);
|
2017-01-12 07:12:01 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->scanFiles();
|
2017-02-15 06:15:51 +01:00
|
|
|
|
|
|
|
if (!$this->server_mode) {
|
|
|
|
$this->analyzeFiles();
|
|
|
|
}
|
2016-10-07 06:58:08 +02:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$removed_parser_files = $this->cache_provider->deleteOldParserCaches(
|
|
|
|
$is_diff ? $this->cache_provider->getLastGoodRun() : $start_checks
|
2016-11-06 05:59:29 +01:00
|
|
|
);
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($this->debug_output && $removed_parser_files) {
|
2016-11-06 05:59:29 +01:00
|
|
|
echo 'Removed ' . $removed_parser_files . ' old parser caches' . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($is_diff) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->cache_provider->touchParserCaches($this->getAllFiles($this->config), $start_checks);
|
2016-11-06 05:59:29 +01:00
|
|
|
}
|
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($this->collect_references) {
|
2017-02-27 07:30:44 +01:00
|
|
|
if ($this->find_references_to) {
|
|
|
|
if (strpos($this->find_references_to, '::') !== false) {
|
2017-02-28 00:24:20 +01:00
|
|
|
$locations_by_files = $this->findReferencesToMethod($this->find_references_to);
|
2017-02-27 07:30:44 +01:00
|
|
|
} else {
|
2017-02-28 00:24:20 +01:00
|
|
|
$locations_by_files = $this->findReferencesToClassLike($this->find_references_to);
|
2017-02-27 07:30:44 +01:00
|
|
|
}
|
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
foreach ($locations_by_files as $locations) {
|
2017-03-02 07:10:00 +01:00
|
|
|
$bounds_starts = [];
|
2017-03-02 04:27:52 +01:00
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
foreach ($locations as $location) {
|
|
|
|
$snippet = $location->getSnippet();
|
2017-02-27 07:30:44 +01:00
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
$snippet_bounds = $location->getSnippetBounds();
|
|
|
|
$selection_bounds = $location->getSelectionBounds();
|
2017-02-27 07:30:44 +01:00
|
|
|
|
2017-03-02 07:10:00 +01:00
|
|
|
if (isset($bounds_starts[$selection_bounds[0]])) {
|
2017-03-02 04:27:52 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-02 07:10:00 +01:00
|
|
|
$bounds_starts[$selection_bounds[0]] = true;
|
2017-03-02 04:27:52 +01:00
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
$selection_start = $selection_bounds[0] - $snippet_bounds[0];
|
|
|
|
$selection_length = $selection_bounds[1] - $selection_bounds[0];
|
2017-02-27 07:30:44 +01:00
|
|
|
|
2017-02-28 00:24:20 +01:00
|
|
|
echo $location->file_name . ':' . $location->getLineNumber() . PHP_EOL .
|
2017-12-01 01:00:09 +01:00
|
|
|
(
|
|
|
|
$this->use_color
|
2017-02-28 00:24:20 +01:00
|
|
|
? substr($snippet, 0, $selection_start) .
|
|
|
|
"\e[97;42m" . substr($snippet, $selection_start, $selection_length) .
|
|
|
|
"\e[0m" . substr($snippet, $selection_length + $selection_start)
|
|
|
|
: $snippet
|
|
|
|
) . PHP_EOL . PHP_EOL;
|
|
|
|
}
|
2017-02-27 07:30:44 +01:00
|
|
|
}
|
2017-03-03 21:54:08 +01:00
|
|
|
} else {
|
|
|
|
$this->checkClassReferences();
|
2017-02-27 07:30:44 +01:00
|
|
|
}
|
2017-01-30 05:44:05 +01:00
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
IssueBuffer::finish($this, true, (int)$start_checks, $this->scanned_files);
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
public function scanFiles()
|
2017-01-02 21:31:18 +01:00
|
|
|
{
|
|
|
|
if (!$this->config) {
|
|
|
|
throw new \UnexpectedValueException('$this->config cannot be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
$filetype_handlers = $this->config->getFiletypeHandlers();
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$has_changes = false;
|
|
|
|
|
|
|
|
while ($this->files_to_scan || $this->classes_to_scan) {
|
|
|
|
if ($this->files_to_scan) {
|
|
|
|
$file_path = array_shift($this->files_to_scan);
|
|
|
|
|
|
|
|
if (!isset($this->scanned_files[$file_path])) {
|
|
|
|
$this->scanFile($file_path, $filetype_handlers, isset($this->files_to_deep_scan[$file_path]));
|
|
|
|
$has_changes = true;
|
|
|
|
}
|
|
|
|
} else {
|
2017-11-28 06:46:41 +01:00
|
|
|
/** @var string */
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_classlike_name = array_shift($this->classes_to_scan);
|
|
|
|
$fq_classlike_name_lc = strtolower($fq_classlike_name);
|
|
|
|
|
|
|
|
if (isset($this->reflected_classeslikes_lc[$fq_classlike_name_lc])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
|
|
|
|
&& $this->existing_classlikes_lc[$fq_classlike_name_lc] === false
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($this->classlike_files[$fq_classlike_name_lc])) {
|
|
|
|
if (isset($this->existing_classlikes_lc[$fq_classlike_name_lc])
|
|
|
|
&& $this->existing_classlikes_lc[$fq_classlike_name_lc]
|
|
|
|
) {
|
|
|
|
$reflected_class = new \ReflectionClass($fq_classlike_name);
|
|
|
|
ClassLikeChecker::registerReflectedClass($reflected_class->name, $reflected_class, $this);
|
|
|
|
$this->reflected_classeslikes_lc[$fq_classlike_name_lc] = true;
|
|
|
|
} elseif ($this->fileExistsForClassLike($fq_classlike_name)) {
|
|
|
|
if (isset($this->classlike_files[$fq_classlike_name_lc])) {
|
|
|
|
$file_path = $this->classlike_files[$fq_classlike_name_lc];
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
if (isset($this->classes_to_deep_scan[$fq_classlike_name_lc])) {
|
|
|
|
unset($this->classes_to_deep_scan[$fq_classlike_name_lc]);
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
}
|
|
|
|
}
|
2017-07-27 22:59:41 +02:00
|
|
|
} elseif ($this->store_scan_failure[$fq_classlike_name]) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->existing_classlikes_lc[$fq_classlike_name_lc] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$has_changes) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'ClassLikeStorage is populating' . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
foreach ($this->classlike_storage_provider->getAll() as $storage) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!$storage->user_defined) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->populateClassLikeStorage($storage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'ClassLikeStorage is populated' . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'FileStorage is populating' . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$all_file_storage = $this->file_storage_provider->getAll();
|
|
|
|
|
|
|
|
foreach ($all_file_storage as $file_storage) {
|
|
|
|
$this->populateFileStorage($file_storage);
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'FileStorage is populated' . PHP_EOL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ClassLikeStorage $storage
|
|
|
|
* @param array $dependent_classlikes
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function populateClassLikeStorage(ClassLikeStorage $storage, $dependent_classlikes = [])
|
|
|
|
{
|
|
|
|
if ($storage->populated) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($dependent_classlikes[strtolower($storage->name)])) {
|
|
|
|
throw new CircularReferenceException(
|
|
|
|
'Circular reference discovered when loading ' . $storage->name
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$storage_provider = $this->classlike_storage_provider;
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$dependent_classlikes[strtolower($storage->name)] = true;
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if (isset($storage->parent_classes[0])) {
|
|
|
|
try {
|
|
|
|
$parent_storage = $storage_provider->get($storage->parent_classes[0]);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
$parent_storage = null;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if ($parent_storage) {
|
|
|
|
$this->populateClassLikeStorage($parent_storage, $dependent_classlikes);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$this->inheritMethodsFromParent($storage, $parent_storage);
|
|
|
|
$this->inheritPropertiesFromParent($storage, $parent_storage);
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$storage->class_implements += $parent_storage->class_implements;
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$storage->public_class_constants += $parent_storage->public_class_constants;
|
|
|
|
$storage->protected_class_constants += $parent_storage->protected_class_constants;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$parent_interfaces = [];
|
|
|
|
|
|
|
|
foreach ($storage->parent_interfaces as $parent_interface_lc => $_) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$parent_interface_storage = $storage_provider->get($parent_interface_lc);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
$this->populateClassLikeStorage($parent_interface_storage, $dependent_classlikes);
|
|
|
|
|
|
|
|
// copy over any constants
|
|
|
|
$storage->public_class_constants = array_merge(
|
|
|
|
$storage->public_class_constants,
|
|
|
|
$parent_interface_storage->public_class_constants
|
|
|
|
);
|
|
|
|
|
|
|
|
$parent_interfaces = array_merge($parent_interfaces, $parent_interface_storage->parent_interfaces);
|
|
|
|
|
|
|
|
$this->inheritMethodsFromParent($storage, $parent_interface_storage);
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->parent_interfaces = array_merge($parent_interfaces, $storage->parent_interfaces);
|
|
|
|
|
|
|
|
$extra_interfaces = [];
|
|
|
|
|
|
|
|
foreach ($storage->class_implements as $implemented_interface_lc => $_) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$implemented_interface_storage = $storage_provider->get($implemented_interface_lc);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-07-25 22:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->populateClassLikeStorage($implemented_interface_storage, $dependent_classlikes);
|
|
|
|
|
|
|
|
// copy over any constants
|
|
|
|
$storage->public_class_constants = array_merge(
|
|
|
|
$storage->public_class_constants,
|
|
|
|
$implemented_interface_storage->public_class_constants
|
|
|
|
);
|
|
|
|
|
|
|
|
$extra_interfaces = array_merge($extra_interfaces, $implemented_interface_storage->parent_interfaces);
|
|
|
|
|
|
|
|
$storage->public_class_constants += $implemented_interface_storage->public_class_constants;
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->class_implements = array_merge($extra_interfaces, $storage->class_implements);
|
|
|
|
|
|
|
|
foreach ($storage->class_implements as $implemented_interface) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$implemented_interface_storage = $storage_provider->get($implemented_interface);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-07-25 22:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($implemented_interface_storage->methods as $method_name => $method) {
|
|
|
|
if ($method->visibility === ClassLikeChecker::VISIBILITY_PUBLIC) {
|
|
|
|
$mentioned_method_id = $implemented_interface . '::' . $method_name;
|
|
|
|
$implemented_method_id = $storage->name . '::' . $method_name;
|
|
|
|
|
2017-11-26 22:03:17 +01:00
|
|
|
if ($storage->abstract) {
|
|
|
|
MethodChecker::setOverriddenMethodId($this, $implemented_method_id, $mentioned_method_id);
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($storage->used_traits as $used_trait_lc => $used_trait) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$trait_storage = $storage_provider->get($used_trait_lc);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-07-25 22:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->populateClassLikeStorage($trait_storage, $dependent_classlikes);
|
|
|
|
|
|
|
|
$this->inheritMethodsFromParent($storage, $trait_storage);
|
|
|
|
$this->inheritPropertiesFromParent($storage, $trait_storage);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($storage->location) {
|
|
|
|
$file_path = $storage->location->file_path;
|
|
|
|
|
|
|
|
foreach ($storage->parent_interfaces as $parent_interface_lc) {
|
|
|
|
FileReferenceProvider::addFileInheritanceToClass($file_path, $parent_interface_lc);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($storage->parent_classes as $parent_class_lc) {
|
|
|
|
FileReferenceProvider::addFileInheritanceToClass($file_path, $parent_class_lc);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($storage->class_implements as $implemented_interface) {
|
|
|
|
FileReferenceProvider::addFileInheritanceToClass($file_path, strtolower($implemented_interface));
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($storage->used_traits as $used_trait_lc) {
|
|
|
|
FileReferenceProvider::addFileInheritanceToClass($file_path, $used_trait_lc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->populated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param FileStorage $storage
|
2017-12-09 20:53:39 +01:00
|
|
|
* @param array<string, bool> $dependent_file_paths
|
2017-07-25 22:11:02 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
private function populateFileStorage(FileStorage $storage, array $dependent_file_paths = [])
|
|
|
|
{
|
|
|
|
if ($storage->populated) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($dependent_file_paths[strtolower($storage->file_path)])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$dependent_file_paths[strtolower($storage->file_path)] = true;
|
|
|
|
|
|
|
|
foreach ($storage->included_file_paths as $included_file_path => $_) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$included_file_storage = $this->file_storage_provider->get($included_file_path);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-07-25 22:11:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->populateFileStorage($included_file_storage, $dependent_file_paths);
|
|
|
|
|
|
|
|
$storage->declaring_function_ids = array_merge(
|
|
|
|
$included_file_storage->declaring_function_ids,
|
|
|
|
$storage->declaring_function_ids
|
|
|
|
);
|
2017-07-28 16:42:30 +02:00
|
|
|
|
|
|
|
$storage->declaring_constants = array_merge(
|
|
|
|
$included_file_storage->declaring_constants,
|
|
|
|
$storage->declaring_constants
|
|
|
|
);
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$storage->populated = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-11-09 05:32:22 +01:00
|
|
|
* @param ClassLikeStorage $storage
|
|
|
|
* @param ClassLikeStorage $parent_storage
|
2017-07-25 22:11:02 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
protected function inheritMethodsFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage)
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
|
|
|
$fq_class_name = $storage->name;
|
|
|
|
|
|
|
|
// register where they appear (can never be in a trait)
|
|
|
|
foreach ($parent_storage->appearing_method_ids as $method_name => $appearing_method_id) {
|
|
|
|
if ($parent_storage->is_trait
|
|
|
|
&& $storage->trait_alias_map
|
|
|
|
&& isset($storage->trait_alias_map[$method_name])
|
|
|
|
) {
|
|
|
|
$aliased_method_name = $storage->trait_alias_map[$method_name];
|
|
|
|
} else {
|
|
|
|
$aliased_method_name = $method_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($storage->appearing_method_ids[$aliased_method_name])) {
|
|
|
|
continue;
|
2017-02-15 06:15:51 +01:00
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
$implemented_method_id = $fq_class_name . '::' . $aliased_method_name;
|
|
|
|
|
|
|
|
$storage->appearing_method_ids[$aliased_method_name] =
|
|
|
|
$parent_storage->is_trait ? $implemented_method_id : $appearing_method_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// register where they're declared
|
2017-11-09 03:27:23 +01:00
|
|
|
foreach ($parent_storage->inheritable_method_ids as $method_name => $declaring_method_id) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!$parent_storage->is_trait) {
|
|
|
|
$implemented_method_id = $fq_class_name . '::' . $method_name;
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
MethodChecker::setOverriddenMethodId($this, $implemented_method_id, $declaring_method_id);
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($parent_storage->is_trait
|
|
|
|
&& $storage->trait_alias_map
|
|
|
|
&& isset($storage->trait_alias_map[$method_name])
|
|
|
|
) {
|
|
|
|
$aliased_method_name = $storage->trait_alias_map[$method_name];
|
|
|
|
} else {
|
|
|
|
$aliased_method_name = $method_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($storage->declaring_method_ids[$aliased_method_name])) {
|
2017-07-29 05:53:06 +02:00
|
|
|
list($implementing_fq_class_name, $implementing_method_name) = explode(
|
2017-07-29 05:38:57 +02:00
|
|
|
'::',
|
|
|
|
$storage->declaring_method_ids[$aliased_method_name]
|
|
|
|
);
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$implementing_class_storage = $this->classlike_storage_provider->get($implementing_fq_class_name);
|
2017-07-29 05:38:57 +02:00
|
|
|
|
2017-07-29 05:53:06 +02:00
|
|
|
if (!$implementing_class_storage->methods[$implementing_method_name]->abstract) {
|
2017-07-29 05:38:57 +02:00
|
|
|
continue;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$storage->declaring_method_ids[$aliased_method_name] = $declaring_method_id;
|
2017-11-09 03:27:23 +01:00
|
|
|
$storage->inheritable_method_ids[$aliased_method_name] = $declaring_method_id;
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
/**
|
2017-11-09 05:32:22 +01:00
|
|
|
* @param ClassLikeStorage $storage
|
|
|
|
* @param ClassLikeStorage $parent_storage
|
2017-07-25 22:11:02 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-07-29 21:05:06 +02:00
|
|
|
protected function inheritPropertiesFromParent(ClassLikeStorage $storage, ClassLikeStorage $parent_storage)
|
2017-07-25 22:11:02 +02:00
|
|
|
{
|
|
|
|
// register where they appear (can never be in a trait)
|
|
|
|
foreach ($parent_storage->appearing_property_ids as $property_name => $appearing_property_id) {
|
|
|
|
if (isset($storage->appearing_property_ids[$property_name])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$parent_storage->is_trait
|
|
|
|
&& isset($parent_storage->properties[$property_name])
|
|
|
|
&& $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$implemented_property_id = $storage->name . '::$' . $property_name;
|
|
|
|
|
|
|
|
$storage->appearing_property_ids[$property_name] =
|
|
|
|
$parent_storage->is_trait ? $implemented_property_id : $appearing_property_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// register where they're declared
|
|
|
|
foreach ($parent_storage->declaring_property_ids as $property_name => $declaring_property_id) {
|
|
|
|
if (isset($storage->declaring_property_ids[$property_name])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$parent_storage->is_trait
|
|
|
|
&& isset($parent_storage->properties[$property_name])
|
|
|
|
&& $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->declaring_property_ids[$property_name] = $declaring_property_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
// register where they're declared
|
|
|
|
foreach ($parent_storage->inheritable_property_ids as $property_name => $inheritable_property_id) {
|
|
|
|
if (!$parent_storage->is_trait
|
|
|
|
&& isset($parent_storage->properties[$property_name])
|
|
|
|
&& $parent_storage->properties[$property_name]->visibility === ClassLikeChecker::VISIBILITY_PRIVATE
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$storage->inheritable_property_ids[$property_name] = $inheritable_property_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_classlike_name
|
2017-07-27 22:12:16 +02:00
|
|
|
* @param string|null $referencing_file_path
|
2017-07-25 22:11:02 +02:00
|
|
|
* @param bool $analyze_too
|
2017-07-27 22:59:41 +02:00
|
|
|
* @param bool $store_failure
|
2017-07-25 22:11:02 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-07-27 22:59:41 +02:00
|
|
|
public function queueClassLikeForScanning(
|
|
|
|
$fq_classlike_name,
|
|
|
|
$referencing_file_path = null,
|
|
|
|
$analyze_too = false,
|
|
|
|
$store_failure = true
|
|
|
|
) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!$this->config) {
|
|
|
|
throw new \UnexpectedValueException('Config should not be null here');
|
|
|
|
}
|
|
|
|
|
|
|
|
$fq_classlike_name_lc = strtolower($fq_classlike_name);
|
|
|
|
|
|
|
|
if (!isset($this->classlike_files[$fq_classlike_name_lc])) {
|
2017-07-27 22:59:41 +02:00
|
|
|
if (!isset($this->classes_to_scan[$fq_classlike_name_lc]) || $store_failure) {
|
|
|
|
$this->classes_to_scan[$fq_classlike_name_lc] = $fq_classlike_name;
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
|
|
|
|
if ($analyze_too) {
|
2017-07-28 01:14:16 +02:00
|
|
|
$this->classes_to_deep_scan[$fq_classlike_name_lc] = true;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2017-07-27 22:59:41 +02:00
|
|
|
|
|
|
|
$this->store_scan_failure[$fq_classlike_name] = $store_failure;
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
2017-07-27 22:12:16 +02:00
|
|
|
|
|
|
|
if ($referencing_file_path) {
|
|
|
|
FileReferenceProvider::addFileReferenceToClass($referencing_file_path, $fq_classlike_name_lc);
|
|
|
|
}
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function queueFileForScanning($file_path)
|
|
|
|
{
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private function analyzeFiles()
|
2017-01-02 21:31:18 +01:00
|
|
|
{
|
2017-01-08 18:55:32 +01:00
|
|
|
if (!$this->config) {
|
|
|
|
throw new \UnexpectedValueException('$this->config cannot be null');
|
|
|
|
}
|
|
|
|
|
|
|
|
$filetype_handlers = $this->config->getFiletypeHandlers();
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$analysis_worker =
|
|
|
|
/**
|
|
|
|
* @param int $i
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
function ($i, $file_path) use ($filetype_handlers) {
|
|
|
|
$file_checker = $this->getFile($file_path, $filetype_handlers, true);
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'Analyzing ' . $file_checker->getFilePath() . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
$file_checker->analyze(null, $this->update_docblocks);
|
|
|
|
};
|
|
|
|
|
|
|
|
$pool_size = $this->threads;
|
|
|
|
|
|
|
|
if ($pool_size > 1 && count($this->files_to_report) > $pool_size) {
|
|
|
|
$process_file_paths = [];
|
|
|
|
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($this->files_to_report as $file_path) {
|
|
|
|
$process_file_paths[$i % $pool_size][] = $file_path;
|
|
|
|
++$i;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run analysis one file at a time, splitting the set of
|
|
|
|
// files up among a given number of child processes.
|
|
|
|
$pool = new \Psalm\Fork\Pool(
|
|
|
|
$process_file_paths,
|
|
|
|
/** @return void */
|
|
|
|
function () {
|
|
|
|
},
|
|
|
|
$analysis_worker,
|
|
|
|
/** @return array */
|
|
|
|
function () {
|
2017-08-22 18:38:38 +02:00
|
|
|
return [
|
|
|
|
'issues' => IssueBuffer::getIssuesData(),
|
|
|
|
'file_references' => FileReferenceProvider::getAllFileReferences(),
|
|
|
|
];
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Wait for all tasks to complete and collect the results.
|
|
|
|
/**
|
2017-11-16 02:45:53 +01:00
|
|
|
* @var array<array{issues: array<int, array{severity: string, line_number: string, type: string,
|
|
|
|
* message: string, file_name: string, file_path: string, snippet: string, from: int, to: int,
|
|
|
|
* snippet_from: int, snippet_to: int, column: int}>, file_references: array<string, array<string,bool>>}>
|
2017-07-25 22:11:02 +02:00
|
|
|
*/
|
2017-08-22 18:38:38 +02:00
|
|
|
$forked_pool_data = $pool->wait();
|
2017-07-25 22:11:02 +02:00
|
|
|
|
2017-08-22 18:38:38 +02:00
|
|
|
foreach ($forked_pool_data as $pool_data) {
|
|
|
|
IssueBuffer::addIssues($pool_data['issues']);
|
|
|
|
FileReferenceProvider::addFileReferences($pool_data['file_references']);
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
2017-11-09 05:14:27 +01:00
|
|
|
// TODO: Tell the caller that the fork pool encountered an error in another PR?
|
|
|
|
// $did_fork_pool_have_error = $pool->didHaveError();
|
2017-07-25 22:11:02 +02:00
|
|
|
} else {
|
|
|
|
$i = 0;
|
|
|
|
|
|
|
|
foreach ($this->files_to_report as $file_path => $_) {
|
|
|
|
$analysis_worker($i, $file_path);
|
|
|
|
++$i;
|
|
|
|
}
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
2017-09-16 18:45:11 +02:00
|
|
|
|
|
|
|
if ($this->update_docblocks) {
|
|
|
|
foreach ($this->files_to_report as $file_path) {
|
2017-09-16 18:52:46 +02:00
|
|
|
$this->updateFile($file_path, true);
|
2017-09-16 18:45:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-09-16 18:52:46 +02:00
|
|
|
* @param bool $output_changes to console
|
2017-09-16 18:45:11 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2017-09-16 18:52:46 +02:00
|
|
|
public function updateFile($file_path, $output_changes = false)
|
2017-09-16 18:45:11 +02:00
|
|
|
{
|
|
|
|
$file_manipulations = FunctionDocblockManipulator::getManipulationsForFile($file_path);
|
|
|
|
|
2017-09-16 18:52:46 +02:00
|
|
|
$docblock_update_count = count($file_manipulations);
|
|
|
|
|
2017-09-16 18:45:11 +02:00
|
|
|
$existing_contents = $this->getFileContents($file_path);
|
|
|
|
|
|
|
|
foreach (array_reverse($file_manipulations) as $manipulation) {
|
|
|
|
$existing_contents
|
|
|
|
= substr($existing_contents, 0, $manipulation->start)
|
|
|
|
. $manipulation->insertion_text
|
|
|
|
. substr($existing_contents, $manipulation->end);
|
|
|
|
}
|
|
|
|
|
2017-09-16 18:52:46 +02:00
|
|
|
if ($docblock_update_count) {
|
|
|
|
if ($output_changes) {
|
|
|
|
echo 'Adding/updating ' . $docblock_update_count . ' docblocks in ' . $file_path . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->file_provider->setContents($file_path, $existing_contents);
|
|
|
|
}
|
2016-06-13 21:33:18 +02:00
|
|
|
}
|
|
|
|
|
2017-02-27 07:30:44 +01:00
|
|
|
/**
|
|
|
|
* @param string $method_id
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-28 00:24:20 +01:00
|
|
|
* @return array<string, \Psalm\CodeLocation[]>
|
2017-02-27 07:30:44 +01:00
|
|
|
*/
|
|
|
|
public function findReferencesToMethod($method_id)
|
|
|
|
{
|
|
|
|
list($fq_class_name, $method_name) = explode('::', $method_id);
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-02-27 07:30:44 +01:00
|
|
|
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isset($class_storage->methods[strtolower($method_name)])) {
|
|
|
|
die('Method ' . $method_id . ' cannot be found' . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
$method_storage = $class_storage->methods[strtolower($method_name)];
|
|
|
|
|
|
|
|
if ($method_storage->referencing_locations === null) {
|
|
|
|
die('No references found for ' . $method_id . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $method_storage->referencing_locations;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-28 00:24:20 +01:00
|
|
|
* @return array<string, \Psalm\CodeLocation[]>
|
2017-02-27 07:30:44 +01:00
|
|
|
*/
|
|
|
|
public function findReferencesToClassLike($fq_class_name)
|
|
|
|
{
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$class_storage = $this->classlike_storage_provider->get($fq_class_name);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
2017-02-27 07:30:44 +01:00
|
|
|
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($class_storage->referencing_locations === null) {
|
|
|
|
die('No references found for ' . $fq_class_name . PHP_EOL);
|
|
|
|
}
|
|
|
|
|
2017-02-28 06:29:28 +01:00
|
|
|
$classlike_references_by_file = $class_storage->referencing_locations;
|
|
|
|
|
|
|
|
if (isset($this->use_referencing_locations[strtolower($fq_class_name)])) {
|
|
|
|
foreach ($this->use_referencing_locations[strtolower($fq_class_name)] as $file_path => $locations) {
|
|
|
|
if (!isset($classlike_references_by_file[$file_path])) {
|
|
|
|
$classlike_references_by_file[$file_path] = $locations;
|
|
|
|
} else {
|
|
|
|
$classlike_references_by_file[$file_path] = array_merge(
|
|
|
|
$locations,
|
|
|
|
$classlike_references_by_file[$file_path]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $classlike_references_by_file;
|
2017-02-27 07:30:44 +01:00
|
|
|
}
|
|
|
|
|
2017-02-08 06:28:26 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function checkClassReferences()
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
foreach ($this->existing_classlikes_lc as $fq_class_name_lc => $_) {
|
2017-07-29 21:05:06 +02:00
|
|
|
try {
|
|
|
|
$classlike_storage = $this->classlike_storage_provider->get($fq_class_name_lc);
|
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
continue;
|
|
|
|
}
|
2017-02-08 06:28:26 +01:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
if ($classlike_storage->location &&
|
|
|
|
$this->config &&
|
|
|
|
$this->config->isInProjectDirs($classlike_storage->location->file_path)
|
|
|
|
) {
|
|
|
|
if (!isset($this->classlike_references[$fq_class_name_lc])) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UnusedClass(
|
|
|
|
'Class ' . $classlike_storage->name . ' is never used',
|
|
|
|
$classlike_storage->location
|
|
|
|
)
|
|
|
|
)) {
|
|
|
|
// fall through
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
2017-07-29 21:05:06 +02:00
|
|
|
} else {
|
|
|
|
self::checkMethodReferences($classlike_storage);
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param \Psalm\Storage\ClassLikeStorage $classlike_storage
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-08 06:28:26 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected static function checkMethodReferences($classlike_storage)
|
|
|
|
{
|
|
|
|
foreach ($classlike_storage->methods as $method_name => $method_storage) {
|
2017-09-03 00:15:52 +02:00
|
|
|
if (($method_storage->referencing_locations === null
|
|
|
|
|| count($method_storage->referencing_locations) === 0)
|
|
|
|
&& !$classlike_storage->overridden_method_ids[$method_name]
|
|
|
|
&& (substr($method_name, 0, 2) !== '__' || $method_name === '__construct')
|
|
|
|
&& $method_storage->location
|
2017-02-08 06:28:26 +01:00
|
|
|
) {
|
2017-02-24 01:52:23 +01:00
|
|
|
$method_id = $classlike_storage->name . '::' . $method_storage->cased_name;
|
|
|
|
|
|
|
|
if ($method_storage->visibility === ClassLikeChecker::VISIBILITY_PUBLIC) {
|
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new PossiblyUnusedMethod(
|
|
|
|
'Cannot find public calls to method ' . $method_id,
|
|
|
|
$method_storage->location
|
|
|
|
)
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2017-11-11 00:08:17 +01:00
|
|
|
} elseif (!isset($classlike_storage->declaring_method_ids['__call'])) {
|
2017-02-24 01:52:23 +01:00
|
|
|
if (IssueBuffer::accepts(
|
|
|
|
new UnusedMethod(
|
|
|
|
'Method ' . $method_id . ' is never used',
|
|
|
|
$method_storage->location
|
|
|
|
)
|
|
|
|
)) {
|
|
|
|
// fall through
|
|
|
|
}
|
2017-02-08 06:28:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $dir_name
|
2017-05-04 20:25:58 +02:00
|
|
|
* @param string $base_dir
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-05-04 20:25:58 +02:00
|
|
|
public function checkDir($dir_name, $base_dir)
|
2016-06-10 00:08:25 +02:00
|
|
|
{
|
2016-12-08 04:38:57 +01:00
|
|
|
if (!$this->config) {
|
2017-05-04 20:25:58 +02:00
|
|
|
$this->config = $this->getConfigForPath($dir_name, $base_dir);
|
2017-01-18 04:10:21 +01:00
|
|
|
$this->config->hide_external_errors = $this->config->isInProjectDirs($dir_name . DIRECTORY_SEPARATOR);
|
2016-06-26 19:45:20 +02:00
|
|
|
}
|
2016-06-10 00:08:25 +02:00
|
|
|
|
2017-02-18 19:41:27 +01:00
|
|
|
FileReferenceProvider::loadReferenceCache();
|
2016-10-05 19:24:46 +02:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$start_checks = (int)microtime(true);
|
2016-06-26 21:25:38 +02:00
|
|
|
|
2017-01-19 05:34:56 +01:00
|
|
|
$this->checkDirWithConfig($dir_name, $this->config, true);
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->scanFiles();
|
2017-01-02 21:31:18 +01:00
|
|
|
$this->analyzeFiles();
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
IssueBuffer::finish($this, false, $start_checks, $this->scanned_files);
|
2016-06-26 21:25:38 +02:00
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $dir_name
|
|
|
|
* @param Config $config
|
2017-01-19 05:38:21 +01:00
|
|
|
* @param bool $allow_non_project_files
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private function checkDirWithConfig($dir_name, Config $config, $allow_non_project_files = false)
|
2016-06-26 21:25:38 +02:00
|
|
|
{
|
|
|
|
$file_extensions = $config->getFileExtensions();
|
2016-06-13 21:33:18 +02:00
|
|
|
|
|
|
|
/** @var RecursiveDirectoryIterator */
|
2016-06-28 20:28:45 +02:00
|
|
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
2016-06-13 21:33:18 +02:00
|
|
|
$iterator->rewind();
|
|
|
|
|
|
|
|
while ($iterator->valid()) {
|
|
|
|
if (!$iterator->isDot()) {
|
2016-06-20 06:38:13 +02:00
|
|
|
$extension = $iterator->getExtension();
|
2017-05-27 02:05:57 +02:00
|
|
|
if (in_array($extension, $file_extensions, true)) {
|
2017-01-02 21:31:18 +01:00
|
|
|
$file_path = (string)$iterator->getRealPath();
|
2016-12-30 05:37:09 +01:00
|
|
|
|
2017-01-19 05:34:56 +01:00
|
|
|
if ($allow_non_project_files || $config->isInProjectDirs($file_path)) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->files_to_report[$file_path] = $file_path;
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
2016-06-13 21:33:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$iterator->next();
|
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
2016-06-10 00:08:25 +02:00
|
|
|
|
2016-11-07 23:07:59 +01:00
|
|
|
/**
|
|
|
|
* @param Config $config
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-11-07 23:07:59 +01:00
|
|
|
* @return array<int, string>
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private function getAllFiles(Config $config)
|
2016-11-06 05:59:29 +01:00
|
|
|
{
|
|
|
|
$file_extensions = $config->getFileExtensions();
|
|
|
|
$file_names = [];
|
|
|
|
|
2016-12-29 16:24:10 +01:00
|
|
|
foreach ($config->getProjectDirectories() as $dir_name) {
|
2016-11-06 05:59:29 +01:00
|
|
|
/** @var RecursiveDirectoryIterator */
|
|
|
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
|
|
|
$iterator->rewind();
|
|
|
|
|
|
|
|
while ($iterator->valid()) {
|
|
|
|
if (!$iterator->isDot()) {
|
|
|
|
$extension = $iterator->getExtension();
|
2017-05-27 02:05:57 +02:00
|
|
|
if (in_array($extension, $file_extensions, true)) {
|
2016-11-06 05:59:29 +01:00
|
|
|
$file_names[] = (string)$iterator->getRealPath();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$iterator->next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $file_names;
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $dir_name
|
|
|
|
* @param Config $config
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return array<string>
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
protected function getDiffFilesInDir($dir_name, Config $config)
|
2016-10-07 06:58:08 +02:00
|
|
|
{
|
|
|
|
$file_extensions = $config->getFileExtensions();
|
|
|
|
|
|
|
|
/** @var RecursiveDirectoryIterator */
|
|
|
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_name));
|
|
|
|
$iterator->rewind();
|
|
|
|
|
|
|
|
$diff_files = [];
|
|
|
|
|
|
|
|
while ($iterator->valid()) {
|
|
|
|
if (!$iterator->isDot()) {
|
|
|
|
$extension = $iterator->getExtension();
|
2017-05-27 02:05:57 +02:00
|
|
|
if (in_array($extension, $file_extensions, true)) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_path = (string)$iterator->getRealPath();
|
2016-10-07 06:58:08 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($config->isInProjectDirs($file_path)) {
|
|
|
|
if ($this->file_provider->getModifiedTime($file_path) > $this->cache_provider->getLastGoodRun()
|
|
|
|
) {
|
|
|
|
$diff_files[] = $file_path;
|
2016-12-30 05:37:09 +01:00
|
|
|
}
|
2016-10-07 06:58:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$iterator->next();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $diff_files;
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param Config $config
|
2016-11-04 01:51:56 +01:00
|
|
|
* @param array<string> $file_list
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-02-12 00:56:38 +01:00
|
|
|
private function checkDiffFilesWithConfig(Config $config, array $file_list = [])
|
2016-10-05 19:24:46 +02:00
|
|
|
{
|
2017-01-12 07:12:01 +01:00
|
|
|
foreach ($file_list as $file_path) {
|
|
|
|
if (!file_exists($file_path)) {
|
2016-10-09 02:49:14 +02:00
|
|
|
continue;
|
|
|
|
}
|
2016-10-18 16:17:25 +02:00
|
|
|
|
2017-01-16 17:51:42 +01:00
|
|
|
if (!$config->isInProjectDirs($file_path)) {
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($this->debug_output) {
|
2017-05-27 02:05:57 +02:00
|
|
|
echo 'skipping ' . $file_path . PHP_EOL;
|
2016-10-18 16:17:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
2016-10-09 02:49:14 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->files_to_report[$file_path] = $file_path;
|
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
2016-10-05 19:24:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
2017-03-24 23:34:46 +01:00
|
|
|
* @param string $file_path
|
2017-05-04 20:25:58 +02:00
|
|
|
* @param string $base_dir
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-05-04 20:25:58 +02:00
|
|
|
public function checkFile($file_path, $base_dir)
|
2016-06-20 06:38:13 +02:00
|
|
|
{
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($this->debug_output) {
|
2017-03-24 23:34:46 +01:00
|
|
|
echo 'Checking ' . $file_path . PHP_EOL;
|
2016-06-20 06:38:13 +02:00
|
|
|
}
|
2016-06-10 00:08:25 +02:00
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
if (!$this->config) {
|
2017-05-04 20:25:58 +02:00
|
|
|
$this->config = $this->getConfigForPath($file_path, $base_dir);
|
2016-06-26 19:45:20 +02:00
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$start_checks = (int)microtime(true);
|
|
|
|
|
2017-03-24 23:34:46 +01:00
|
|
|
$this->config->hide_external_errors = $this->config->isInProjectDirs($file_path);
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_report[$file_path] = $file_path;
|
2016-09-01 05:12:35 +02:00
|
|
|
|
2017-02-18 19:41:27 +01:00
|
|
|
FileReferenceProvider::loadReferenceCache();
|
2016-10-05 19:24:46 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->scanFiles();
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->analyzeFiles();
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
IssueBuffer::finish($this, false, $start_checks, $this->scanned_files);
|
2017-07-25 22:11:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param array $filetype_handlers
|
|
|
|
* @param bool $will_analyze
|
|
|
|
*
|
|
|
|
* @return FileChecker
|
|
|
|
*/
|
|
|
|
private function getFile($file_path, array $filetype_handlers, $will_analyze = false)
|
|
|
|
{
|
|
|
|
$extension = (string)pathinfo($file_path)['extension'];
|
|
|
|
|
|
|
|
if (isset($filetype_handlers[$extension])) {
|
|
|
|
/** @var FileChecker */
|
|
|
|
$file_checker = new $filetype_handlers[$extension]($file_path, $this);
|
|
|
|
} else {
|
|
|
|
$file_checker = new FileChecker($file_path, $this, $will_analyze);
|
2016-06-10 00:08:25 +02:00
|
|
|
}
|
2016-06-20 06:38:13 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'Getting ' . $file_path . PHP_EOL;
|
|
|
|
}
|
2016-06-21 01:30:38 +02:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
return $file_checker;
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
* @param array $filetype_handlers
|
2017-02-15 06:15:51 +01:00
|
|
|
* @param bool $will_analyze
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-08 18:55:32 +01:00
|
|
|
* @return FileChecker
|
2017-01-02 21:31:18 +01:00
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private function scanFile($file_path, array $filetype_handlers, $will_analyze = false)
|
2017-01-02 21:31:18 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$path_parts = explode(DIRECTORY_SEPARATOR, $file_path);
|
|
|
|
$file_name_parts = explode('.', array_pop($path_parts));
|
2017-08-19 19:30:33 +02:00
|
|
|
$extension = count($file_name_parts) > 1 ? array_pop($file_name_parts) : null;
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
if (isset($filetype_handlers[$extension])) {
|
|
|
|
/** @var FileChecker */
|
2017-02-02 00:11:00 +01:00
|
|
|
$file_checker = new $filetype_handlers[$extension]($file_path, $this);
|
|
|
|
} else {
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_checker = new FileChecker($file_path, $this, $will_analyze);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->scanned_files[$file_path])) {
|
|
|
|
throw new \UnexpectedValueException('Should not be rescanning ' . $file_path);
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$this->file_storage_provider->create($file_path);
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
if ($this->debug_output) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (isset($this->files_to_deep_scan[$file_path])) {
|
|
|
|
echo 'Deep scanning ' . $file_path . PHP_EOL;
|
|
|
|
} else {
|
|
|
|
echo 'Scanning ' . $file_path . PHP_EOL;
|
|
|
|
}
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->scanned_files[$file_path] = true;
|
2017-01-08 19:21:21 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_checker->scan();
|
2017-01-08 18:55:32 +01:00
|
|
|
|
|
|
|
return $file_checker;
|
2017-01-02 21:31:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-09 05:58:06 +01:00
|
|
|
* Checks whether a class exists, and if it does then records what file it's in
|
|
|
|
* for later checking
|
|
|
|
*
|
2017-01-02 21:31:18 +01:00
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-05-25 04:07:49 +02:00
|
|
|
* @return bool
|
2017-01-02 21:31:18 +01:00
|
|
|
* @psalm-suppress MixedMethodCall due to Reflection class weirdness
|
|
|
|
*/
|
2017-01-09 05:58:06 +01:00
|
|
|
public function fileExistsForClassLike($fq_class_name)
|
2017-01-02 21:31:18 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-01-02 21:31:18 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (isset($this->classlike_files[$fq_class_name_lc])) {
|
|
|
|
return true;
|
2017-02-01 01:21:33 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (isset($this->existing_classlikes_lc[$fq_class_name_lc])) {
|
|
|
|
throw new \InvalidArgumentException('Why are you asking about a builtin class?');
|
2017-02-01 01:21:33 +01:00
|
|
|
}
|
|
|
|
|
2017-01-02 21:31:18 +01:00
|
|
|
$old_level = error_reporting();
|
2017-07-10 05:34:16 +02:00
|
|
|
|
|
|
|
if (!$this->debug_output) {
|
2017-07-25 22:11:02 +02:00
|
|
|
error_reporting(E_ERROR);
|
2017-07-10 05:34:16 +02:00
|
|
|
}
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
try {
|
|
|
|
$reflected_class = new \ReflectionClass($fq_class_name);
|
|
|
|
} catch (\ReflectionException $e) {
|
|
|
|
error_reporting($old_level);
|
|
|
|
|
2017-03-16 15:58:13 +01:00
|
|
|
// do not cache any results here (as case-sensitive filenames can screw things up)
|
2017-01-02 21:31:18 +01:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
error_reporting($old_level);
|
|
|
|
|
2017-03-19 23:20:48 +01:00
|
|
|
$file_path = (string)$reflected_class->getFileName();
|
|
|
|
|
|
|
|
// if the file was autoloaded but exists in evaled code only, return false
|
|
|
|
if (!file_exists($file_path)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$fq_class_name = $reflected_class->getName();
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
2017-02-01 01:21:33 +01:00
|
|
|
$this->existing_classlikes[$fq_class_name] = true;
|
|
|
|
|
|
|
|
if ($reflected_class->isInterface()) {
|
2017-03-19 23:20:48 +01:00
|
|
|
$this->addFullyQualifiedInterfaceName($fq_class_name, $file_path);
|
2017-02-01 01:21:33 +01:00
|
|
|
} elseif ($reflected_class->isTrait()) {
|
2017-03-19 23:20:48 +01:00
|
|
|
$this->addFullyQualifiedTraitName($fq_class_name, $file_path);
|
2017-01-09 05:58:06 +01:00
|
|
|
} else {
|
2017-03-19 23:20:48 +01:00
|
|
|
$this->addFullyQualifiedClassName($fq_class_name, $file_path);
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-01-12 06:54:41 +01:00
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function enableCache()
|
|
|
|
{
|
|
|
|
$this->cache = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function disableCache()
|
|
|
|
{
|
|
|
|
$this->cache = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function canCache()
|
|
|
|
{
|
|
|
|
return $this->cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $original_method_id
|
|
|
|
* @param Context $this_context
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-12 06:54:41 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function getMethodMutations($original_method_id, Context $this_context)
|
|
|
|
{
|
2017-02-01 05:24:33 +01:00
|
|
|
list($fq_class_name) = explode('::', $original_method_id);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_checker = $this->getFileCheckerForClassLike($fq_class_name);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-09-14 05:57:11 +02:00
|
|
|
$appearing_method_id = MethodChecker::getAppearingMethodId($this, $original_method_id);
|
|
|
|
|
|
|
|
if (!$appearing_method_id) {
|
|
|
|
// this can happen for some abstract classes implementing (but not fully) interfaces
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-13 04:51:39 +02:00
|
|
|
list($appearing_fq_class_name) = explode('::', $appearing_method_id);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-07-29 21:05:06 +02:00
|
|
|
$appearing_class_storage = $this->classlike_storage_provider->get($appearing_fq_class_name);
|
2017-07-09 21:19:05 +02:00
|
|
|
|
|
|
|
if (!$appearing_class_storage->user_defined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-13 04:51:39 +02:00
|
|
|
if (strtolower($appearing_fq_class_name) !== strtolower($fq_class_name)) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_checker = $this->getFileCheckerForClassLike($appearing_fq_class_name);
|
2017-01-12 06:54:41 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$stmts = $file_checker->getStatements();
|
|
|
|
|
|
|
|
$file_checker->populateCheckers($stmts);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
|
|
|
if (!$this_context->self) {
|
|
|
|
$this_context->self = $fq_class_name;
|
|
|
|
$this_context->vars_in_scope['$this'] = Type::parseString($fq_class_name);
|
|
|
|
}
|
|
|
|
|
2017-06-13 04:51:39 +02:00
|
|
|
$file_checker->getMethodMutations($appearing_method_id, $this_context);
|
2017-01-12 06:54:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-12 06:54:41 +01:00
|
|
|
* @return FileChecker
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
private function getFileCheckerForClassLike($fq_class_name)
|
2017-01-12 06:54:41 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-01-30 06:26:17 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
// this registers the class if it's not user defined
|
|
|
|
if (!$this->fileExistsForClassLike($fq_class_name)) {
|
|
|
|
throw new \UnexpectedValueException('File does not exist for ' . $fq_class_name);
|
|
|
|
}
|
2017-01-12 06:54:41 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->classlike_files[$fq_class_name_lc])) {
|
|
|
|
throw new \UnexpectedValueException('Class ' . $fq_class_name . ' is not user-defined');
|
2017-01-12 06:54:41 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_path = $this->classlike_files[$fq_class_name_lc];
|
|
|
|
|
2017-01-12 06:54:41 +01:00
|
|
|
if ($this->cache && isset($this->file_checkers[$file_path])) {
|
|
|
|
return $this->file_checkers[$file_path];
|
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
$file_checker = new FileChecker($file_path, $this, true);
|
2017-01-12 06:54:41 +01:00
|
|
|
|
|
|
|
if ($this->cache) {
|
|
|
|
$this->file_checkers[$file_path] = $file_checker;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $file_checker;
|
|
|
|
}
|
|
|
|
|
2016-06-26 19:45:20 +02:00
|
|
|
/**
|
|
|
|
* Gets a Config object from an XML file.
|
2016-11-02 07:29:00 +01:00
|
|
|
*
|
|
|
|
* Searches up a folder hierarchy for the most immediate config.
|
2016-06-26 19:45:20 +02:00
|
|
|
*
|
|
|
|
* @param string $path
|
2017-05-04 20:25:58 +02:00
|
|
|
* @param string $base_dir
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
|
|
|
* @throws Exception\ConfigException if a config path is not found
|
|
|
|
*
|
2016-06-26 19:45:20 +02:00
|
|
|
* @return Config
|
|
|
|
*/
|
2017-10-07 20:41:16 +02:00
|
|
|
public function getConfigForPath($path, $base_dir)
|
2016-06-26 19:45:20 +02:00
|
|
|
{
|
2017-01-18 15:36:16 +01:00
|
|
|
$dir_path = realpath($path);
|
2016-06-26 19:45:20 +02:00
|
|
|
|
|
|
|
if (!is_dir($dir_path)) {
|
2017-01-18 15:36:16 +01:00
|
|
|
$dir_path = dirname($dir_path);
|
2016-06-26 19:45:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$config = null;
|
|
|
|
|
|
|
|
do {
|
2017-01-18 15:36:16 +01:00
|
|
|
$maybe_path = $dir_path . DIRECTORY_SEPARATOR . Config::DEFAULT_FILE_NAME;
|
2016-06-26 19:45:20 +02:00
|
|
|
|
|
|
|
if (file_exists($maybe_path)) {
|
2017-05-04 20:25:58 +02:00
|
|
|
$config = Config::loadFromXMLFile($maybe_path, $base_dir);
|
2016-07-26 21:03:15 +02:00
|
|
|
|
2016-06-26 19:45:20 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-01-18 15:36:16 +01:00
|
|
|
$dir_path = dirname($dir_path);
|
|
|
|
} while (dirname($dir_path) !== $dir_path);
|
2016-06-26 19:45:20 +02:00
|
|
|
|
|
|
|
if (!$config) {
|
2017-02-13 05:59:33 +01:00
|
|
|
if ($this->output_format === self::TYPE_CONSOLE) {
|
2017-04-28 06:31:55 +02:00
|
|
|
exit(
|
|
|
|
'Could not locate a config XML file in path ' . $path . '. Have you run \'psalm --init\' ?' .
|
|
|
|
PHP_EOL
|
|
|
|
);
|
2017-02-13 05:59:33 +01:00
|
|
|
}
|
|
|
|
|
2016-06-26 19:45:20 +02:00
|
|
|
throw new Exception\ConfigException('Config not found for path ' . $path);
|
|
|
|
}
|
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$this->config = $config;
|
|
|
|
|
2017-10-15 18:38:47 +02:00
|
|
|
$this->cache_provider->use_igbinary = $config->use_igbinary;
|
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$config->visitStubFiles($this);
|
2017-01-02 21:31:18 +01:00
|
|
|
$config->initializePlugins($this);
|
|
|
|
|
2016-06-26 19:45:20 +02:00
|
|
|
return $config;
|
|
|
|
}
|
2016-06-26 21:33:51 +02:00
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
/**
|
|
|
|
* @param Config $config
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-02-01 01:21:33 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setConfig(Config $config)
|
|
|
|
{
|
|
|
|
$this->config = $config;
|
|
|
|
|
2017-10-15 18:38:47 +02:00
|
|
|
$this->cache_provider->use_igbinary = $config->use_igbinary;
|
|
|
|
|
2017-02-01 01:21:33 +01:00
|
|
|
$config->visitStubFiles($this);
|
|
|
|
$config->initializePlugins($this);
|
|
|
|
}
|
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param string $path_to_config
|
2017-05-04 20:25:58 +02:00
|
|
|
* @param string $base_dir
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
|
|
|
* @throws Exception\ConfigException if a config file is not found in the given location
|
|
|
|
*
|
2016-11-02 07:29:00 +01:00
|
|
|
* @return void
|
2016-10-15 06:12:57 +02:00
|
|
|
*/
|
2017-05-04 20:25:58 +02:00
|
|
|
public function setConfigXML($path_to_config, $base_dir)
|
2016-06-26 21:33:51 +02:00
|
|
|
{
|
|
|
|
if (!file_exists($path_to_config)) {
|
|
|
|
throw new Exception\ConfigException('Config not found at location ' . $path_to_config);
|
|
|
|
}
|
|
|
|
|
2017-05-04 20:25:58 +02:00
|
|
|
$this->config = Config::loadFromXMLFile($path_to_config, $base_dir);
|
2017-02-13 05:59:33 +01:00
|
|
|
|
|
|
|
$this->config->visitStubFiles($this);
|
|
|
|
$this->config->initializePlugins($this);
|
2016-06-26 21:33:51 +02:00
|
|
|
}
|
2016-10-05 19:24:46 +02:00
|
|
|
|
2016-10-15 06:12:57 +02:00
|
|
|
/**
|
|
|
|
* @param array<string> $diff_files
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-10-15 06:12:57 +02:00
|
|
|
* @return array<string>
|
|
|
|
*/
|
2016-10-05 19:24:46 +02:00
|
|
|
public static function getReferencedFilesFromDiff(array $diff_files)
|
|
|
|
{
|
2016-10-05 23:08:20 +02:00
|
|
|
$all_inherited_files_to_check = $diff_files;
|
2016-10-05 19:24:46 +02:00
|
|
|
|
|
|
|
while ($diff_files) {
|
|
|
|
$diff_file = array_shift($diff_files);
|
|
|
|
|
2017-02-18 19:41:27 +01:00
|
|
|
$dependent_files = FileReferenceProvider::getFilesInheritingFromFile($diff_file);
|
2016-10-05 23:08:20 +02:00
|
|
|
$new_dependent_files = array_diff($dependent_files, $all_inherited_files_to_check);
|
|
|
|
|
|
|
|
$all_inherited_files_to_check += $new_dependent_files;
|
|
|
|
$diff_files += $new_dependent_files;
|
|
|
|
}
|
|
|
|
|
|
|
|
$all_files_to_check = $all_inherited_files_to_check;
|
|
|
|
|
|
|
|
foreach ($all_inherited_files_to_check as $file_name) {
|
2017-02-18 19:41:27 +01:00
|
|
|
$dependent_files = FileReferenceProvider::getFilesReferencingFile($file_name);
|
2016-10-05 23:08:20 +02:00
|
|
|
$all_files_to_check = array_merge($dependent_files, $all_files_to_check);
|
2016-10-05 19:24:46 +02:00
|
|
|
}
|
|
|
|
|
2016-10-05 23:08:20 +02:00
|
|
|
return array_unique($all_files_to_check);
|
2016-10-05 19:24:46 +02:00
|
|
|
}
|
2016-12-08 04:38:57 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-08 04:38:57 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
public function registerAnalyzableFile($file_path)
|
2016-12-08 04:38:57 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->files_to_deep_scan[$file_path] = $file_path;
|
|
|
|
$this->files_to_report[$file_path] = $file_path;
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
|
|
|
|
2017-01-19 18:15:42 +01:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-07-25 22:11:02 +02:00
|
|
|
* @return array<int, \PhpParser\Node\Stmt>
|
2017-01-19 18:15:42 +01:00
|
|
|
*/
|
2017-07-25 22:11:02 +02:00
|
|
|
public function getStatementsForFile($file_path)
|
|
|
|
{
|
|
|
|
return StatementsProvider::getStatementsForFile(
|
|
|
|
$file_path,
|
|
|
|
$this->file_provider,
|
|
|
|
$this->cache_provider,
|
|
|
|
$this->debug_output
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function fileExists($file_path)
|
2017-01-19 18:15:42 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
return $this->file_provider->fileExists($file_path);
|
2017-01-19 18:15:42 +01:00
|
|
|
}
|
|
|
|
|
2016-12-08 04:38:57 +01:00
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2016-12-08 04:38:57 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getFileContents($file_path)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
return $this->file_provider->getContents($file_path);
|
2016-12-08 04:38:57 +01:00
|
|
|
}
|
2017-01-09 05:58:06 +01:00
|
|
|
|
|
|
|
/**
|
2017-01-19 18:15:42 +01:00
|
|
|
* @param string $fq_class_name
|
|
|
|
* @param string|null $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-19 16:55:58 +01:00
|
|
|
public function addFullyQualifiedClassName($fq_class_name, $file_path = null)
|
2017-01-09 05:58:06 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = false;
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
2017-01-09 05:58:06 +01:00
|
|
|
$this->existing_classes[$fq_class_name] = true;
|
2017-01-19 16:55:58 +01:00
|
|
|
|
|
|
|
if ($file_path) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->classlike_files[$fq_class_name_lc] = $file_path;
|
2017-01-19 16:55:58 +01:00
|
|
|
}
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 18:15:42 +01:00
|
|
|
* @param string $fq_class_name
|
|
|
|
* @param string|null $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-19 16:55:58 +01:00
|
|
|
public function addFullyQualifiedInterfaceName($fq_class_name, $file_path = null)
|
2017-01-09 05:58:06 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = false;
|
2017-01-09 05:58:06 +01:00
|
|
|
$this->existing_interfaces[$fq_class_name] = true;
|
2017-01-19 16:55:58 +01:00
|
|
|
|
|
|
|
if ($file_path) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->classlike_files[$fq_class_name_lc] = $file_path;
|
2017-01-19 16:55:58 +01:00
|
|
|
}
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-01-19 18:15:42 +01:00
|
|
|
* @param string $fq_class_name
|
|
|
|
* @param string|null $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return void
|
|
|
|
*/
|
2017-01-19 16:55:58 +01:00
|
|
|
public function addFullyQualifiedTraitName($fq_class_name, $file_path = null)
|
2017-01-09 05:58:06 +01:00
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
|
|
|
$this->existing_classlikes_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_traits_lc[$fq_class_name_lc] = true;
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
2017-01-09 05:58:06 +01:00
|
|
|
$this->existing_traits[$fq_class_name] = true;
|
2017-01-19 16:55:58 +01:00
|
|
|
|
|
|
|
if ($file_path) {
|
2017-07-25 22:11:02 +02:00
|
|
|
$this->classlike_files[$fq_class_name_lc] = $file_path;
|
2017-01-19 16:55:58 +01:00
|
|
|
}
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasFullyQualifiedClassName($fq_class_name)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-01-30 04:30:27 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->existing_classes_lc[$fq_class_name_lc])
|
|
|
|
|| !$this->existing_classes_lc[$fq_class_name_lc]
|
2017-07-29 21:05:06 +02:00
|
|
|
|| !$this->classlike_storage_provider->has($fq_class_name_lc)
|
2017-01-30 04:30:27 +01:00
|
|
|
) {
|
2017-12-01 01:00:09 +01:00
|
|
|
if ((
|
|
|
|
!isset($this->existing_classes_lc[$fq_class_name_lc])
|
2017-07-25 22:11:02 +02:00
|
|
|
|| $this->existing_classes_lc[$fq_class_name_lc] === true
|
|
|
|
)
|
2017-07-29 21:05:06 +02:00
|
|
|
&& !$this->classlike_storage_provider->has($fq_class_name_lc)
|
2017-07-25 22:11:02 +02:00
|
|
|
) {
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'Last-chance attempt to hydrate ' . $fq_class_name . PHP_EOL;
|
|
|
|
}
|
|
|
|
// attempt to load in the class
|
|
|
|
$this->queueClassLikeForScanning($fq_class_name);
|
|
|
|
$this->scanFiles();
|
|
|
|
|
|
|
|
if (!isset($this->existing_classes_lc[$fq_class_name_lc])) {
|
|
|
|
$this->existing_classes_lc[$fq_class_name_lc] = false;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->existing_classes_lc[$fq_class_name_lc];
|
|
|
|
}
|
|
|
|
|
2017-01-30 04:30:27 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($this->collect_references) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->classlike_references[$fq_class_name_lc])) {
|
|
|
|
$this->classlike_references[$fq_class_name_lc] = 0;
|
2017-01-30 05:44:05 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
++$this->classlike_references[$fq_class_name_lc];
|
2017-01-30 04:30:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasFullyQualifiedInterfaceName($fq_class_name)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-01-30 04:30:27 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->existing_interfaces_lc[$fq_class_name_lc])
|
|
|
|
|| !$this->existing_interfaces_lc[$fq_class_name_lc]
|
2017-07-29 21:05:06 +02:00
|
|
|
|| !$this->classlike_storage_provider->has($fq_class_name_lc)
|
2017-01-30 04:30:27 +01:00
|
|
|
) {
|
2017-12-01 01:00:09 +01:00
|
|
|
if ((
|
|
|
|
!isset($this->existing_classes_lc[$fq_class_name_lc])
|
2017-07-25 22:11:02 +02:00
|
|
|
|| $this->existing_classes_lc[$fq_class_name_lc] === true
|
|
|
|
)
|
2017-07-29 21:05:06 +02:00
|
|
|
&& !$this->classlike_storage_provider->has($fq_class_name_lc)
|
2017-07-25 22:11:02 +02:00
|
|
|
) {
|
|
|
|
if ($this->debug_output) {
|
|
|
|
echo 'Last-chance attempt to hydrate ' . $fq_class_name . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// attempt to load in the class
|
|
|
|
$this->queueClassLikeForScanning($fq_class_name);
|
|
|
|
$this->scanFiles();
|
|
|
|
|
|
|
|
if (!isset($this->existing_interfaces_lc[$fq_class_name_lc])) {
|
|
|
|
$this->existing_interfaces_lc[$fq_class_name_lc] = false;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->existing_interfaces_lc[$fq_class_name_lc];
|
|
|
|
}
|
|
|
|
|
2017-01-30 04:30:27 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($this->collect_references) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->classlike_references[$fq_class_name_lc])) {
|
|
|
|
$this->classlike_references[$fq_class_name_lc] = 0;
|
2017-01-30 05:44:05 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
++$this->classlike_references[$fq_class_name_lc];
|
2017-01-30 04:30:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $fq_class_name
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-01-09 05:58:06 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function hasFullyQualifiedTraitName($fq_class_name)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
$fq_class_name_lc = strtolower($fq_class_name);
|
2017-01-30 04:30:27 +01:00
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->existing_traits_lc[$fq_class_name_lc]) ||
|
|
|
|
!$this->existing_traits_lc[$fq_class_name_lc]
|
2017-01-30 04:30:27 +01:00
|
|
|
) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-27 05:09:18 +01:00
|
|
|
if ($this->collect_references) {
|
2017-07-25 22:11:02 +02:00
|
|
|
if (!isset($this->classlike_references[$fq_class_name_lc])) {
|
|
|
|
$this->classlike_references[$fq_class_name_lc] = 0;
|
2017-01-30 05:44:05 +01:00
|
|
|
}
|
|
|
|
|
2017-07-25 22:11:02 +02:00
|
|
|
++$this->classlike_references[$fq_class_name_lc];
|
2017-01-30 04:30:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2017-01-09 05:58:06 +01:00
|
|
|
}
|
2017-03-24 23:34:46 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $file_path
|
2017-05-27 02:16:18 +02:00
|
|
|
*
|
2017-03-24 23:34:46 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function canReportIssues($file_path)
|
|
|
|
{
|
2017-07-25 22:11:02 +02:00
|
|
|
return isset($this->files_to_report[$file_path]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function collectPredefinedClassLikes()
|
|
|
|
{
|
|
|
|
/** @var array<int, string> */
|
|
|
|
$predefined_classes = get_declared_classes();
|
|
|
|
|
|
|
|
foreach ($predefined_classes as $predefined_class) {
|
|
|
|
$reflection_class = new \ReflectionClass($predefined_class);
|
|
|
|
|
|
|
|
if (!$reflection_class->isUserDefined()) {
|
|
|
|
$predefined_class_lc = strtolower($predefined_class);
|
|
|
|
$this->existing_classlikes_lc[$predefined_class_lc] = true;
|
|
|
|
$this->existing_classes_lc[$predefined_class_lc] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var array<int, string> */
|
|
|
|
$predefined_interfaces = get_declared_interfaces();
|
|
|
|
|
|
|
|
foreach ($predefined_interfaces as $predefined_interface) {
|
|
|
|
$reflection_class = new \ReflectionClass($predefined_interface);
|
|
|
|
|
|
|
|
if (!$reflection_class->isUserDefined()) {
|
|
|
|
$predefined_interface_lc = strtolower($predefined_interface);
|
|
|
|
$this->existing_classlikes_lc[$predefined_interface_lc] = true;
|
|
|
|
$this->existing_interfaces_lc[$predefined_interface_lc] = true;
|
|
|
|
}
|
|
|
|
}
|
2017-03-24 23:34:46 +01:00
|
|
|
}
|
2016-06-10 00:08:25 +02:00
|
|
|
}
|