1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-12 09:19:40 +01:00
psalm/src/Psalm/Config.php

843 lines
23 KiB
PHP
Raw Normal View History

2016-06-06 07:07:50 +02:00
<?php
2016-07-26 00:37:44 +02:00
namespace Psalm;
2016-06-06 07:07:50 +02:00
use Psalm\Checker\ClassLikeChecker;
use Psalm\Checker\FileChecker;
use Psalm\Checker\ProjectChecker;
2016-12-30 02:07:42 +01:00
use Psalm\Config\IssueHandler;
use Psalm\Config\ProjectFileFilter;
2016-11-02 07:29:00 +01:00
use Psalm\Exception\ConfigException;
2016-06-10 00:08:25 +02:00
use SimpleXMLElement;
2016-06-06 07:07:50 +02:00
class Config
{
2016-07-26 00:39:36 +02:00
const DEFAULT_FILE_NAME = 'psalm.xml';
2016-06-25 00:18:11 +02:00
const REPORT_INFO = 'info';
const REPORT_ERROR = 'error';
2016-06-26 19:45:20 +02:00
const REPORT_SUPPRESS = 'suppress';
2016-06-25 00:18:11 +02:00
2016-11-02 07:29:00 +01:00
/**
* @var array<string>
*/
2016-06-27 21:10:13 +02:00
public static $ERROR_LEVELS = [
2016-06-25 00:18:11 +02:00
self::REPORT_INFO,
self::REPORT_ERROR,
2017-05-27 02:05:57 +02:00
self::REPORT_SUPPRESS,
2016-06-25 00:18:11 +02:00
];
/**
* @var array
*/
protected static $MIXED_ISSUES = [
'MixedArgument',
'MixedArrayAccess',
'MixedArrayAssignment',
'MixedArrayOffset',
'MixedAssignment',
'MixedInferredReturnType',
'MixedMethodCall',
'MixedOperand',
'MixedPropertyFetch',
'MixedPropertyAssignment',
2017-05-27 02:05:57 +02:00
'MixedStringOffsetAssignment',
'MixedTypeCoercion',
];
2016-11-02 07:29:00 +01:00
/**
* @var self|null
*/
protected static $config;
2016-06-10 00:08:25 +02:00
/**
* Whether or not to stop when the first error is seen
2016-11-01 05:39:41 +01:00
*
2017-05-27 02:16:18 +02:00
* @var bool
*/
public $stop_on_first_error = true;
/**
* Whether or not to use types as defined in docblocks
2016-11-01 05:39:41 +01:00
*
2017-05-27 02:16:18 +02:00
* @var bool
*/
2016-08-24 06:22:38 +02:00
public $use_docblock_types = true;
2016-06-10 00:08:25 +02:00
/**
* Whether or not to throw an exception on first error
2016-11-01 05:39:41 +01:00
*
2017-05-27 02:16:18 +02:00
* @var bool
*/
public $throw_exception = false;
/**
2016-11-04 22:45:12 +01:00
* The directory to store PHP Parser (and other) caches
*
* @var string
*/
public $cache_directory;
2016-11-04 22:45:12 +01:00
/**
* Whether or not to use property defaults to inform type when none is listed
*
2017-05-27 02:16:18 +02:00
* @var bool
*/
public $use_property_default_for_type = false;
/**
* Whether or not to care about casing of file names
*
2017-05-27 02:16:18 +02:00
* @var bool
*/
public $use_case_sensitive_file_names = false;
2016-11-04 22:45:12 +01:00
/**
* Path to the autoader
2016-11-01 05:39:41 +01:00
*
* @var string|null
*/
public $autoloader;
2016-11-02 07:29:00 +01:00
/**
2016-12-30 02:07:42 +01:00
* @var ProjectFileFilter|null
2016-11-02 07:29:00 +01:00
*/
2017-07-25 23:04:58 +02:00
protected $project_files;
2016-06-10 00:08:25 +02:00
/**
* The base directory of this config file
2016-11-01 05:39:41 +01:00
*
* @var string
*/
2017-07-25 23:04:58 +02:00
protected $base_dir;
2016-06-06 07:07:50 +02:00
/**
* The path to this config file
*
* @var string
*/
public $file_path;
2016-11-02 07:29:00 +01:00
/**
* @var array<int, string>
*/
private $file_extensions = ['php'];
2016-11-02 07:29:00 +01:00
/**
* @var array<string, string>
2016-11-02 07:29:00 +01:00
*/
private $filetype_handlers = [];
2016-09-09 22:21:49 +02:00
/**
2016-12-30 02:07:42 +01:00
* @var array<string, IssueHandler>
2016-09-09 22:21:49 +02:00
*/
private $issue_handlers = [];
2016-11-02 07:29:00 +01:00
/**
* @var array<int, string>
*/
private $mock_classes = [];
2016-06-06 07:07:50 +02:00
2017-02-01 01:21:33 +01:00
/**
* @var array<int, string>
*/
private $stub_files = [];
2017-02-01 01:21:33 +01:00
/**
* @var bool
*/
public $cache_file_hashes_during_run = true;
2016-11-02 07:29:00 +01:00
/**
2017-05-27 02:16:18 +02:00
* @var bool
2016-11-02 07:29:00 +01:00
*/
public $hide_external_errors = true;
2016-12-07 00:27:22 +01:00
/** @var bool */
public $allow_includes = true;
/** @var bool */
public $totally_typed = false;
/** @var bool */
public $strict_binary_operands = false;
/** @var bool */
public $add_void_docblocks = true;
2017-01-28 21:17:14 +01:00
/**
* If true, assert() calls can be used to check types of variables
*
2017-05-27 02:16:18 +02:00
* @var bool
2017-01-28 21:17:14 +01:00
*/
public $use_assert_for_type = false;
/**
2017-05-27 02:16:18 +02:00
* @var bool
*/
public $remember_property_assignments_after_call = true;
/** @var bool */
public $use_igbinary = false;
2016-06-18 20:45:55 +02:00
/**
2016-07-26 00:37:44 +02:00
* Psalm plugins
2016-11-02 07:29:00 +01:00
*
2016-06-18 20:45:55 +02:00
* @var array<Plugin>
*/
private $plugins = [];
2016-06-18 20:45:55 +02:00
2017-02-01 01:21:33 +01:00
/** @var array<string, bool> */
private $predefined_classlikes = [];
2017-02-01 01:21:33 +01:00
2016-11-21 03:49:06 +01:00
/** @var array<string, mixed> */
private $predefined_constants;
2016-11-21 03:49:06 +01:00
/** @var array<string, bool> */
private $predefined_functions = [];
2016-12-14 18:28:38 +01:00
protected function __construct()
2016-06-06 07:07:50 +02:00
{
2016-11-02 07:29:00 +01:00
self::$config = $this;
2016-06-06 07:07:50 +02:00
}
2016-06-26 19:45:20 +02:00
/**
* Creates a new config object from the file
2016-11-02 07:29:00 +01:00
*
2017-02-01 01:21:33 +01:00
* @param string $file_path
* @param string $base_dir
2017-05-27 02:16:18 +02:00
*
2016-12-29 16:24:10 +01:00
* @return self
*/
public static function loadFromXMLFile($file_path, $base_dir)
2016-12-29 16:24:10 +01:00
{
$file_contents = file_get_contents($file_path);
if ($file_contents === false) {
throw new \InvalidArgumentException('Cannot open ' . $file_path);
}
return self::loadFromXML($file_path, $base_dir, $file_contents);
2016-12-29 16:24:10 +01:00
}
/**
* Creates a new config object from an XML string
*
2017-02-01 01:21:33 +01:00
* @param string $file_path
* @param string $base_dir
2017-02-01 01:21:33 +01:00
* @param string $file_contents
2017-05-27 02:16:18 +02:00
*
2016-11-01 05:39:41 +01:00
* @return self
2016-11-05 02:14:04 +01:00
* @psalm-suppress MixedArgument
* @psalm-suppress MixedPropertyFetch
* @psalm-suppress MixedMethodCall
2016-12-17 06:48:31 +01:00
* @psalm-suppress MixedAssignment
2016-12-24 12:03:55 +01:00
* @psalm-suppress MixedOperand
* @psalm-suppress MixedPropertyAssignment
2016-06-26 19:45:20 +02:00
*/
public static function loadFromXML($file_path, $base_dir, $file_contents)
2016-06-10 00:08:25 +02:00
{
2017-02-01 01:21:33 +01:00
$config = new static();
$config->file_path = $file_path;
$config->base_dir = $base_dir;
$schema_path = dirname(dirname(__DIR__)) . '/config.xsd';
if (!file_exists($schema_path)) {
throw new ConfigException('Cannot locate config schema');
}
$dom_document = new \DOMDocument();
$dom_document->loadXML($file_contents);
// Enable user error handling
libxml_use_internal_errors(true);
if (!$dom_document->schemaValidate($schema_path)) {
$errors = libxml_get_errors();
foreach ($errors as $error) {
if ($error->level === LIBXML_ERR_FATAL || $error->level === LIBXML_ERR_ERROR) {
throw new ConfigException(
'Error parsing file ' . $error->file . ' on line ' . $error->line . ': ' . $error->message
);
}
}
libxml_clear_errors();
}
2016-06-10 00:08:25 +02:00
$config_xml = new SimpleXMLElement($file_contents);
if (isset($config_xml['stopOnFirstError'])) {
$attribute_text = (string) $config_xml['stopOnFirstError'];
$config->stop_on_first_error = $attribute_text === 'true' || $attribute_text === '1';
2016-06-10 00:08:25 +02:00
}
2016-06-25 00:18:11 +02:00
if (isset($config_xml['useDocblockTypes'])) {
$attribute_text = (string) $config_xml['useDocblockTypes'];
$config->use_docblock_types = $attribute_text === 'true' || $attribute_text === '1';
2016-06-10 00:08:25 +02:00
}
if (isset($config_xml['throwExceptionOnError'])) {
$attribute_text = (string) $config_xml['throwExceptionOnError'];
$config->throw_exception = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['hideExternalErrors'])) {
$attribute_text = (string) $config_xml['hideExternalErrors'];
$config->hide_external_errors = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['autoloader'])) {
$config->autoloader = (string) $config_xml['autoloader'];
}
2016-11-04 22:45:12 +01:00
if (isset($config_xml['cacheDirectory'])) {
$config->cache_directory = (string)$config_xml['cacheDirectory'];
} else {
$config->cache_directory = sys_get_temp_dir() . '/psalm';
2016-11-04 22:45:12 +01:00
}
if (@mkdir($config->cache_directory, 0777, true) === false && is_dir($config->cache_directory) === false) {
trigger_error('Could not create cache directory: ' . $config->cache_directory, E_USER_ERROR);
exit(255);
}
if (isset($config_xml['usePropertyDefaultForType'])) {
$attribute_text = (string) $config_xml['usePropertyDefaultForType'];
$config->use_property_default_for_type = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['allowFileIncludes'])) {
$attribute_text = (string) $config_xml['allowFileIncludes'];
$config->allow_includes = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['totallyTyped'])) {
$attribute_text = (string) $config_xml['totallyTyped'];
$config->totally_typed = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['strictBinaryOperands'])) {
$attribute_text = (string) $config_xml['strictBinaryOperands'];
$config->strict_binary_operands = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['requireVoidReturnType'])) {
$attribute_text = (string) $config_xml['requireVoidReturnType'];
$config->add_void_docblocks = $attribute_text === 'true' || $attribute_text === '1';
}
2017-01-28 21:17:14 +01:00
if (isset($config_xml['useAssertForType'])) {
$attribute_text = (string) $config_xml['useAssertForType'];
$config->use_assert_for_type = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['cacheFileContentHashes'])) {
$attribute_text = (string) $config_xml['cacheFileContentHashes'];
$config->cache_file_hashes_during_run = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['rememberPropertyAssignmentsAfterCall'])) {
$attribute_text = (string) $config_xml['rememberPropertyAssignmentsAfterCall'];
$config->remember_property_assignments_after_call = $attribute_text === 'true' || $attribute_text === '1';
}
if (isset($config_xml['serializer'])) {
$attribute_text = (string) $config_xml['serializer'];
$config->use_igbinary = $attribute_text === 'igbinary';
}
2016-12-29 14:42:39 +01:00
if (isset($config_xml->projectFiles)) {
$config->project_files = ProjectFileFilter::loadFromXMLElement($config_xml->projectFiles, $base_dir, true);
2016-06-10 00:08:25 +02:00
}
if (isset($config_xml->fileExtensions)) {
$config->file_extensions = [];
$config->loadFileExtensions($config_xml->fileExtensions->extension);
}
if (isset($config_xml->mockClasses) && isset($config_xml->mockClasses->class)) {
/** @var \SimpleXMLElement $mock_class */
foreach ($config_xml->mockClasses->class as $mock_class) {
2017-06-13 06:51:16 +02:00
$config->mock_classes[] = (string)$mock_class['name'];
2016-06-10 00:08:25 +02:00
}
}
2017-02-01 01:21:33 +01:00
if (isset($config_xml->stubs) && isset($config_xml->stubs->file)) {
/** @var \SimpleXMLElement $stub_file */
foreach ($config_xml->stubs->file as $stub_file) {
$file_path = realpath($stub_file['name']);
if (!$file_path) {
throw new Exception\ConfigException(
'Cannot resolve stubfile path ' . getcwd() . '/' . $stub_file['name']
);
2017-02-01 01:21:33 +01:00
}
$config->stub_files[] = $file_path;
}
}
2016-06-18 20:45:55 +02:00
// this plugin loading system borrows heavily from etsy/phan
if (isset($config_xml->plugins) && isset($config_xml->plugins->plugin)) {
/** @var \SimpleXMLElement $plugin */
2016-06-18 20:45:55 +02:00
foreach ($config_xml->plugins->plugin as $plugin) {
$plugin_file_name = $plugin['filename'];
$path = $config->base_dir . $plugin_file_name;
$config->addPluginPath($path);
2016-06-18 20:45:55 +02:00
}
}
2016-12-30 02:07:42 +01:00
if (isset($config_xml->issueHandlers)) {
/** @var \SimpleXMLElement $issue_handler */
2016-12-30 02:07:42 +01:00
foreach ($config_xml->issueHandlers->children() as $key => $issue_handler) {
/** @var string $key */
$config->issue_handlers[$key] = IssueHandler::loadFromXMLElement($issue_handler, $base_dir);
2016-06-10 00:08:25 +02:00
}
}
2016-06-26 19:45:20 +02:00
2017-02-01 01:21:33 +01:00
if ($config->autoloader) {
/** @psalm-suppress UnresolvableInclude */
require_once($base_dir . DIRECTORY_SEPARATOR . $config->autoloader);
2017-02-01 01:21:33 +01:00
}
$config->collectPredefinedConstants();
$config->collectPredefinedFunctions();
2016-06-26 19:45:20 +02:00
return $config;
2016-06-10 00:08:25 +02:00
}
/**
* @return $this
*/
2016-06-06 07:07:50 +02:00
public static function getInstance()
{
2016-11-02 07:29:00 +01:00
if (self::$config) {
return self::$config;
2016-06-06 07:07:50 +02:00
}
return new self();
}
/**
* @param string $issue_key
* @param string $error_level
2017-05-27 02:16:18 +02:00
*
* @return void
*/
public function setCustomErrorLevel($issue_key, $error_level)
{
2016-12-30 02:07:42 +01:00
$this->issue_handlers[$issue_key] = new IssueHandler();
$this->issue_handlers[$issue_key]->setErrorLevel($error_level);
}
2016-10-30 17:46:18 +01:00
/**
* @param array<\SimpleXMLElement> $extensions
2017-05-27 02:16:18 +02:00
*
* @throws ConfigException if a Config file could not be found
*
2016-10-30 17:46:18 +01:00
* @return void
*/
private function loadFileExtensions($extensions)
{
foreach ($extensions as $extension) {
2016-11-05 02:14:04 +01:00
$extension_name = preg_replace('/^\.?/', '', (string)$extension['name']);
$this->file_extensions[] = $extension_name;
if (isset($extension['filetypeHandler'])) {
2016-12-24 12:03:55 +01:00
$path = $this->base_dir . (string)$extension['filetypeHandler'];
if (!file_exists($path)) {
2016-06-20 07:05:44 +02:00
throw new Exception\ConfigException('Error parsing config: cannot find file ' . $path);
}
$this->filetype_handlers[$extension_name] = $path;
}
}
}
/**
* Initialises all the plugins (done once the config is fully loaded)
2017-05-27 02:16:18 +02:00
*
* @return void
* @psalm-suppress MixedArrayAccess
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedOperand
*/
public function initializePlugins(ProjectChecker $project_checker)
{
2017-02-08 00:27:28 +01:00
foreach ($this->filetype_handlers as &$path) {
$project_checker->file_storage_provider->create($path);
$plugin_file_checker = new FileChecker($path, $project_checker);
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$plugin_file_checker->scan();
$declared_classes = ClassLikeChecker::getClassesForFile($project_checker, $path);
if (count($declared_classes) !== 1) {
throw new \InvalidArgumentException(
'Filetype handlers must have exactly one class in the file - ' . $path . ' has ' .
count($declared_classes)
);
}
/** @psalm-suppress UnresolvableInclude */
require_once($path);
if (!\Psalm\Checker\ClassChecker::classExtends(
$project_checker,
$declared_classes[0],
'Psalm\\Checker\\FileChecker'
)
) {
throw new \InvalidArgumentException(
'Filetype handlers must extend \Psalm\Checker\FileChecker - ' . $path . ' does not'
);
}
$path = $declared_classes[0];
}
}
2016-10-09 23:55:21 +02:00
/**
* @param string $file_name
2017-05-27 02:16:18 +02:00
*
2016-10-09 23:55:21 +02:00
* @return string
*/
public function shortenFileName($file_name)
{
2017-01-18 04:10:21 +01:00
return preg_replace('/^' . preg_quote($this->base_dir, DIRECTORY_SEPARATOR) . '/', '', $file_name);
}
2016-11-02 07:29:00 +01:00
/**
* @param string $issue_type
* @param string $file_path
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return bool
*/
public function reportIssueInFile($issue_type, $file_path)
2016-06-10 00:08:25 +02:00
{
2017-05-27 02:05:57 +02:00
if (!$this->totally_typed && in_array($issue_type, self::$MIXED_ISSUES, true)) {
return false;
}
if ($this->hide_external_errors) {
$project_checker = ProjectChecker::getInstance();
if (!$project_checker->canReportIssues($file_path)) {
return false;
2016-08-05 21:11:20 +02:00
}
}
if ($this->getReportingLevelForFile($issue_type, $file_path) === self::REPORT_SUPPRESS) {
return false;
}
2016-06-10 00:08:25 +02:00
return true;
2016-06-10 00:08:25 +02:00
}
2016-11-02 07:29:00 +01:00
/**
* @param string $file_path
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return bool
*/
public function isInProjectDirs($file_path)
{
return $this->project_files && $this->project_files->allows($file_path);
}
2016-11-02 07:29:00 +01:00
/**
* @param string $issue_type
* @param string $file_path
2017-05-27 02:16:18 +02:00
*
2016-11-02 07:29:00 +01:00
* @return string
*/
public function getReportingLevelForFile($issue_type, $file_path)
{
2016-12-30 02:07:42 +01:00
if (isset($this->issue_handlers[$issue_type])) {
return $this->issue_handlers[$issue_type]->getReportingLevelForFile($file_path);
}
return self::REPORT_ERROR;
}
2016-10-15 06:12:57 +02:00
/**
* @return array<string>
*/
2016-12-29 16:24:10 +01:00
public function getProjectDirectories()
2016-06-10 00:08:25 +02:00
{
2016-12-29 14:42:39 +01:00
if (!$this->project_files) {
2016-08-07 02:27:13 +02:00
return [];
}
2016-12-29 16:24:10 +01:00
return $this->project_files->getDirectories();
2016-06-13 21:33:18 +02:00
}
2016-06-10 00:08:25 +02:00
2016-10-15 06:12:57 +02:00
/**
* @return array<string>
*/
2016-06-13 21:33:18 +02:00
public function getFileExtensions()
{
return $this->file_extensions;
2016-06-10 00:08:25 +02:00
}
2016-11-02 07:29:00 +01:00
/**
* @return array<string, string>
2016-11-02 07:29:00 +01:00
*/
public function getFiletypeHandlers()
{
return $this->filetype_handlers;
}
2016-10-09 23:55:21 +02:00
/**
2016-11-01 05:39:41 +01:00
* @return array<int, string>
2016-10-09 23:55:21 +02:00
*/
public function getMockClasses()
{
return $this->mock_classes;
}
2016-06-18 20:45:55 +02:00
2017-02-01 01:21:33 +01:00
/**
* @param ProjectChecker $project_checker
2017-05-27 02:16:18 +02:00
*
2017-02-01 01:21:33 +01:00
* @return void
*/
public function visitStubFiles(ProjectChecker $project_checker)
{
$project_checker->register_global_functions = true;
$generic_stubs = realpath(__DIR__ . '/Stubs/CoreGenericFunctions.php');
if ($generic_stubs) {
$generic_stub_checker = new FileChecker(
$generic_stubs,
$project_checker
);
$project_checker->file_storage_provider->create($generic_stubs);
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$generic_stub_checker->scan();
} else {
throw new \UnexpectedValueException('Cannot locate core generic stubs');
}
2017-02-01 01:21:33 +01:00
foreach ($this->stub_files as $stub_file) {
$stub_checker = new FileChecker($stub_file, $project_checker);
$project_checker->file_storage_provider->create($stub_file);
Refactor scanning and analysis, introducing multithreading (#191) * Add failing test * Add visitor to soup up classlike references * Move a whole bunch of code into the visitor * Move some methods back, move onto analysis stage * Use the getAliases method everywhere * Fix refs * Fix more refs * Fix some tests * Fix more tests * Fix include tests * Shift config class finding to project checker and fix bugs * Fix a few more tests * transition test to new syntax * Remove var_dump * Delete a bunch of code and fix mutation test * Remove unnecessary visitation * Transition to better mocked out file provider, breaking some cached statement loading * Use different scheme for naming anonymous classes * Fix anonymous class issues * Refactor file/statement loading * Add specific property types * Fix mapped property assignment * Improve how we deal with traits * Fix trait checking * Pass Psalm checks * Add multi-process support * Delay console output until the end * Remove PHP 7 syntax * Update file storage with classes * Fix scanning individual files and add reflection return types * Always turn XDebug off * Add quicker method of getting method mutations * Queue return types for crawling * Interpret all strings as possible classes once we see a `get_class` call * Check invalid return types again * Fix template namespacing issues * Default to class-insensitive file names for includes * Don’t overwrite existing issues data * Add var docblocks for scanning * Add null check * Fix loading of external classes in templates * Only try to populate class when we haven’t yet seen it’s not a class * Fix trait property accessibility * Only ever improve docblock param type * Make param replacement more robust * Fix static const missing inferred type * Fix a few more tests * Register constant definitions * Fix trait aliasing * Skip constant type tests for now * Fix linting issues * Make sure caching is off for tests * Remove unnecessary return * Use emulative parser if on PHP 5.6 * Cache parser for faster first-time parse * Fix constant resolution when scanning classes * Remove test that’s beyond a practical scope * Add back --diff support * Add --help for --threads * Remove unused vars
2017-07-25 22:11:02 +02:00
$stub_checker->scan();
2017-02-01 01:21:33 +01:00
}
$project_checker->register_global_functions = false;
2017-02-01 01:21:33 +01:00
}
2016-11-04 22:45:12 +01:00
/**
2016-11-05 02:14:04 +01:00
* @return string
2016-11-04 22:45:12 +01:00
*/
public function getCacheDirectory()
{
return $this->cache_directory;
}
2016-10-09 23:55:21 +02:00
/**
* @return array<Plugin>
*/
2016-06-18 20:45:55 +02:00
public function getPlugins()
{
return $this->plugins;
}
2016-06-20 22:18:31 +02:00
2016-11-21 05:45:10 +01:00
/**
* @return array<string, mixed>
*/
2016-11-21 03:49:06 +01:00
public function getPredefinedConstants()
{
return $this->predefined_constants;
}
2016-11-21 05:45:10 +01:00
/**
* @return void
* @psalm-suppress InvalidPropertyAssignment
2016-11-21 05:45:10 +01:00
*/
2016-11-21 03:49:06 +01:00
public function collectPredefinedConstants()
{
$this->predefined_constants = get_defined_constants();
}
/**
* @return array<string, bool>
*/
public function getPredefinedFunctions()
{
return $this->predefined_functions;
}
/**
* @return void
* @psalm-suppress InvalidPropertyAssignment
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedArrayOffset
*/
public function collectPredefinedFunctions()
{
$defined_functions = get_defined_functions();
if (isset($defined_functions['user'])) {
foreach ($defined_functions['user'] as $function_name) {
$this->predefined_functions[$function_name] = true;
}
}
if (isset($defined_functions['internal'])) {
foreach ($defined_functions['internal'] as $function_name) {
$this->predefined_functions[$function_name] = true;
}
}
}
/**
* @return void
*
* @psalm-suppress MixedAssignment
* @psalm-suppress MixedArrayAccess
*/
public function visitComposerAutoloadFiles(ProjectChecker $project_checker)
{
$project_checker->register_global_functions = true;
$composer_json_path = $this->base_dir . 'composer.json'; // this should ideally not be hardcoded
if (!file_exists($composer_json_path)) {
$project_checker->register_global_functions = false;
return;
}
/** @psalm-suppress PossiblyFalseArgument */
if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) {
throw new \UnexpectedValueException('Invalid composer.json at ' . $composer_json_path);
}
if (isset($composer_json['autoload']['files'])) {
/** @var string[] */
$files = $composer_json['autoload']['files'];
foreach ($files as $file) {
$file_path = realpath($this->base_dir . $file);
if (!$file_path) {
continue;
}
$file_checker = new FileChecker($file_path, $project_checker);
$project_checker->file_storage_provider->create($file_path);
$file_checker->scan();
}
}
$project_checker->register_global_functions = false;
}
/**
* @return array<string, string>
*/
public function getComposerClassMap()
{
$vendor_dir = $this->base_dir . 'vendor'; // this should ideally not be hardcoded
$autoload_files_classmap =
$vendor_dir . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR . 'autoload_classmap.php';
if (!file_exists($autoload_files_classmap)) {
return [];
}
/**
* @psalm-suppress MixedAssignment
* @psalm-suppress UnresolvableInclude
*/
$class_map = include_once $autoload_files_classmap;
if (is_array($class_map)) {
$composer_classmap = array_change_key_case($class_map);
$composer_classmap = array_filter(
$composer_classmap,
/**
* @param string $file_path
*
* @return bool
*/
function ($file_path) use ($vendor_dir) {
return strpos($file_path, $vendor_dir) === 0;
}
);
} else {
$composer_classmap = [];
}
return $composer_classmap;
}
/**
* @param string $dir
*
* @return void
*/
public static function removeCacheDirectory($dir)
{
if (is_dir($dir)) {
$objects = scandir($dir);
if ($objects === false) {
throw new \UnexpectedValueException('Not expecting false here');
}
foreach ($objects as $object) {
if ($object != '.' && $object != '..') {
if (filetype($dir . '/' . $object) == 'dir') {
self::removeCacheDirectory($dir . '/' . $object);
} else {
unlink($dir . '/' . $object);
}
}
}
reset($objects);
rmdir($dir);
}
}
/**
* @param string $path
*
* @return void
*/
public function addPluginPath($path)
{
if (!file_exists($path)) {
throw new \InvalidArgumentException('Cannot find file ' . $path);
}
/**
* @var Plugin
* @psalm-suppress UnresolvableInclude
*/
$loaded_plugin = require($path);
if (!$loaded_plugin) {
throw new \InvalidArgumentException(
'Plugins must return an instance of that plugin at the end of the file - ' .
$plugin_file_name . ' does not'
);
}
if (!($loaded_plugin instanceof Plugin)) {
throw new \InvalidArgumentException(
'Plugins must extend \Psalm\Plugin - ' . $plugin_file_name . ' does not'
);
}
$this->plugins[] = $loaded_plugin;
}
2016-06-06 07:07:50 +02:00
}