mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Create Context, new if checks & file extension support
Add Context object to hold in-scope vars, rework if checks accordingly with copious use of clone, and finally add support for handling different filetypes
This commit is contained in:
parent
5f2d9a66ee
commit
444c39097f
@ -120,16 +120,15 @@ class ClassChecker implements StatementsSource
|
||||
}
|
||||
|
||||
if ($leftover_stmts) {
|
||||
$scope_vars = [];
|
||||
$possibly_in_scope_vars = [];
|
||||
$context = new Context();
|
||||
|
||||
(new StatementsChecker($this))->check($leftover_stmts, $scope_vars, $possibly_in_scope_vars);
|
||||
(new StatementsChecker($this))->check($leftover_stmts, $context);
|
||||
}
|
||||
|
||||
if ($check_statements) {
|
||||
// do the method checks after all class methods have been initialised
|
||||
foreach ($method_checkers as $method_checker) {
|
||||
$method_checker->check();
|
||||
$method_checker->check(new Context());
|
||||
$method_checker->checkReturnTypes();
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +298,6 @@ class ClassMethodChecker extends FunctionChecker
|
||||
'name' => $param->getName(),
|
||||
'by_ref' => $param->isPassedByReference(),
|
||||
'type' => $param_type ? Type::parseString($param_type) : Type::getMixed(),
|
||||
'is_nullable' => $is_nullable
|
||||
];
|
||||
}
|
||||
|
||||
@ -427,36 +426,42 @@ class ClassMethodChecker extends FunctionChecker
|
||||
$param_type = null;
|
||||
|
||||
if ($param->type) {
|
||||
if (is_string($param->type)) {
|
||||
$param_type = $param->type;
|
||||
if ($param->type instanceof Type) {
|
||||
$param_type = $param_type;
|
||||
}
|
||||
else {
|
||||
if ($param->type instanceof PhpParser\Node\Name\FullyQualified) {
|
||||
$param_type = implode('\\', $param->type->parts);
|
||||
|
||||
} elseif ($param->type->parts === ['self']) {
|
||||
$param_type = $this->_absolute_class;
|
||||
|
||||
} else {
|
||||
$param_type = ClassChecker::getAbsoluteClassFromString(implode('\\', $param->type->parts), $this->_namespace, $this->_aliased_classes);
|
||||
if (is_string($param->type)) {
|
||||
$param_type_string = $param->type;
|
||||
}
|
||||
elseif ($param->type instanceof PhpParser\Node\Name\FullyQualified) {
|
||||
$param_type_string = implode('\\', $param->type->parts);
|
||||
}
|
||||
elseif ($param->type->parts === ['self']) {
|
||||
$param_type_string = $this->_absolute_class;
|
||||
}
|
||||
else {
|
||||
$param_type_string = ClassChecker::getAbsoluteClassFromString(implode('\\', $param->type->parts), $this->_namespace, $this->_aliased_classes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$is_nullable = $param->default !== null &&
|
||||
$is_nullable = $param->default !== null &&
|
||||
$param->default instanceof \PhpParser\Node\Expr\ConstFetch &&
|
||||
$param->default->name instanceof PhpParser\Node\Name &&
|
||||
$param->default->name->parts = ['null'];
|
||||
|
||||
if ($is_nullable && $param_type) {
|
||||
$param_type .= '|null';
|
||||
if ($param_type_string) {
|
||||
if ($is_nullable) {
|
||||
$param_type_string .= '|null';
|
||||
}
|
||||
|
||||
$param_type = Type::parseString($param_type_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$_method_params[$method_id][] = [
|
||||
'name' => $param->name,
|
||||
'by_ref' => $param->byRef,
|
||||
'type' => $param_type ? Type::parseString($param_type) : Type::getMixed(),
|
||||
'is_nullable' => $is_nullable
|
||||
'type' => $param_type ?: Type::getMixed(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector;
|
||||
|
||||
class CodeException extends \Exception {
|
||||
|
||||
}
|
@ -18,6 +18,8 @@ class Config
|
||||
|
||||
protected $file_extensions = ['php'];
|
||||
|
||||
protected $filetype_handlers = [];
|
||||
|
||||
protected $issue_handlers = [];
|
||||
|
||||
protected $mock_classes = [];
|
||||
@ -33,39 +35,35 @@ class Config
|
||||
self::$_config = $this;
|
||||
}
|
||||
|
||||
public static function loadFromXML($file_name)
|
||||
public function loadFromXML($file_name)
|
||||
{
|
||||
$config = new self();
|
||||
|
||||
$file_contents = file_get_contents($file_name);
|
||||
|
||||
$config->base_dir = dirname($file_name) . '/';
|
||||
$this->base_dir = dirname($file_name) . '/';
|
||||
|
||||
$config_xml = new SimpleXMLElement($file_contents);
|
||||
|
||||
if (isset($config_xml['stopOnError'])) {
|
||||
$config->stop_on_error = $config_xml['stopOnError'] === 'true' || $config_xml['stopOnError'] === '1';
|
||||
$this->stop_on_error = $config_xml['stopOnError'] === 'true' || $config_xml['stopOnError'] === '1';
|
||||
}
|
||||
|
||||
if (isset($config_xml['useDocblockReturnType'])) {
|
||||
$config->use_docblock_return_type = (bool) $config_xml['useDocblockReturnType'];
|
||||
$this->use_docblock_return_type = (bool) $config_xml['useDocblockReturnType'];
|
||||
}
|
||||
|
||||
if (isset($config_xml->inspectFiles)) {
|
||||
$config->inspect_files = FileFilter::loadFromXML($config_xml->inspectFiles, true);
|
||||
$this->inspect_files = FileFilter::loadFromXML($config_xml->inspectFiles, true);
|
||||
}
|
||||
|
||||
if (isset($config_xml->fileExtensions)) {
|
||||
$config->file_extensions = [];
|
||||
$this->file_extensions = [];
|
||||
|
||||
foreach ($config_xml->fileExtensions->extension as $extension) {
|
||||
$config->file_extensions[] = preg_replace('/^\.?/', '', $extension['name']);
|
||||
}
|
||||
$this->loadFileExtensions($config_xml->fileExtensions->extension);
|
||||
}
|
||||
|
||||
if (isset($config_xml->mockClasses) && isset($config_xml->mockClasses->class)) {
|
||||
foreach ($config_xml->mockClasses->class as $mock_class) {
|
||||
$config->mock_classes[] = $mock_class['name'];
|
||||
$this->mock_classes[] = $mock_class['name'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +71,14 @@ class Config
|
||||
if (isset($config_xml->plugins) && isset($config_xml->plugins->plugin)) {
|
||||
foreach ($config_xml->plugins->plugin as $plugin) {
|
||||
$plugin_file_name = $plugin['filename'];
|
||||
$loaded_plugin = require($config->base_dir . $plugin_file_name);
|
||||
|
||||
$path = $this->base_dir . $plugin_file_name;
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \InvalidArgumentException('Cannot find file ' . $path);
|
||||
}
|
||||
|
||||
$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');
|
||||
@ -83,14 +88,14 @@ class Config
|
||||
throw new \InvalidArgumentException('Plugins must extend \CodeInspector\Plugin - ' . $plugin_file_name . ' does not');
|
||||
}
|
||||
|
||||
$config->plugins[] = $loaded_plugin;
|
||||
$this->plugins[] = $loaded_plugin;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config_xml->issueHandler)) {
|
||||
foreach ($config_xml->issueHandler->children() as $key => $issue_handler) {
|
||||
if (isset($issue_handler->excludeFiles)) {
|
||||
$config->issue_handlers[$key] = FileFilter::loadFromXML($issue_handler->excludeFiles, false);
|
||||
$this->issue_handlers[$key] = FileFilter::loadFromXML($issue_handler->excludeFiles, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,6 +113,36 @@ class Config
|
||||
return new self();
|
||||
}
|
||||
|
||||
protected function loadFileExtensions($extensions)
|
||||
{
|
||||
foreach ($extensions as $extension) {
|
||||
$extension_name = preg_replace('/^\.?/', '', $extension['name']);
|
||||
$this->file_extensions[] = $extension_name;
|
||||
|
||||
if (isset($extension['filetypeHandler'])) {
|
||||
$path = $this->base_dir . $extension['filetypeHandler'];
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \ConfigException('Error parsing config: cannot find file ' . $path);
|
||||
}
|
||||
|
||||
$declared_classes = FileChecker::getDeclaredClassesInFile($path);
|
||||
|
||||
if (count($declared_classes) !== 1) {
|
||||
throw new \InvalidArgumentException('Filetype handlers must have exactly one class in the file - ' . $path . ' has ' . count($declared_classes));
|
||||
}
|
||||
|
||||
require_once($path);
|
||||
|
||||
if (!is_subclass_of($declared_classes[0], 'CodeInspector\\FileChecker')) {
|
||||
throw new \InvalidArgumentException('Filetype handlers must extend \CodeInspector\FileChecker - ' . $path . ' does not');
|
||||
}
|
||||
|
||||
$this->filetype_handlers[$extension_name] = $declared_classes[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function shortenFileName($file_name)
|
||||
{
|
||||
return preg_replace('/^' . preg_quote($this->base_dir, '/') . '/', '', $file_name);
|
||||
@ -145,6 +180,11 @@ class Config
|
||||
return $this->file_extensions;
|
||||
}
|
||||
|
||||
public function getFiletypeHandlers()
|
||||
{
|
||||
return $this->filetype_handlers;
|
||||
}
|
||||
|
||||
public function getMockClasses()
|
||||
{
|
||||
return $this->mock_classes;
|
||||
|
58
src/CodeInspector/Context.php
Normal file
58
src/CodeInspector/Context.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector;
|
||||
|
||||
class Context
|
||||
{
|
||||
public $vars_in_scope = [];
|
||||
|
||||
public $vars_possibly_in_scope = [];
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->vars_in_scope as $key => &$type) {
|
||||
$type = clone $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the parent context, looking at the changes within a block
|
||||
* and then applying those changes, where necessary, to the parent context
|
||||
*
|
||||
* @param Context $start_context
|
||||
* @param Context $end_context
|
||||
* @param bool $has_leaving_statements whether or not the parent scope is abandoned between $start_context and $end_context
|
||||
* @return void
|
||||
*/
|
||||
public function update(Context $start_context, Context $end_context, $has_leaving_statments, array &$updated_vars)
|
||||
{
|
||||
foreach ($this->vars_in_scope as $var => &$context_type) {
|
||||
$old_type = $start_context->vars_in_scope[$var];
|
||||
// if we're leaving, we're effectively deleting the possibility of the if types
|
||||
$new_type = !$has_leaving_statments ? $end_context->vars_in_scope[$var] : null;
|
||||
|
||||
// this is only true if there was some sort of type negation
|
||||
if ((string)$context_type !== (string)$old_type) {
|
||||
|
||||
// if the type changed within the block of statements, process the replacement
|
||||
if ((string)$old_type !== (string)$new_type) {
|
||||
$context_type->substitute($old_type, $new_type);
|
||||
$updated_vars[$var] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getRedefinedVars(Context $original_context, Context $new_context)
|
||||
{
|
||||
$redefined_vars = [];
|
||||
|
||||
foreach ($original_context->vars_in_scope as $var => $context_type) {
|
||||
if ((string)$new_context->vars_in_scope[$var] !== (string)$context_type) {
|
||||
$redefined_vars[$var] = $new_context->vars_in_scope[$var];
|
||||
}
|
||||
}
|
||||
|
||||
return $redefined_vars;
|
||||
}
|
||||
}
|
7
src/CodeInspector/Exception/CodeException.php
Normal file
7
src/CodeInspector/Exception/CodeException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector\Exception;
|
||||
|
||||
class CodeException extends \Exception
|
||||
{
|
||||
}
|
7
src/CodeInspector/Exception/ConfigException.php
Normal file
7
src/CodeInspector/Exception/ConfigException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace CodeInspector\Exception;
|
||||
|
||||
class ConfigException extends \Exception
|
||||
{
|
||||
}
|
@ -17,7 +17,7 @@ class ExceptionHandler
|
||||
$error_message = $error_class_name . ' - ' . $e->getMessage();
|
||||
|
||||
if ($config->stop_on_error) {
|
||||
throw new CodeException($error_message);
|
||||
throw new Exception\CodeException($error_message);
|
||||
}
|
||||
|
||||
echo $error_message . PHP_EOL;
|
||||
|
@ -20,6 +20,8 @@ class FileChecker implements StatementsSource
|
||||
|
||||
protected $_preloaded_statements = [];
|
||||
|
||||
protected $_declared_classes = [];
|
||||
|
||||
protected static $_cache_dir = null;
|
||||
protected static $_file_checkers = [];
|
||||
protected static $_functions = [];
|
||||
@ -51,6 +53,7 @@ class FileChecker implements StatementsSource
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
||||
if ($check_classes) {
|
||||
$class_checker = ClassChecker::getClassCheckerFromClass($stmt->name) ?: new ClassChecker($stmt, $this, $stmt->name);
|
||||
$this->_declared_classes[] = $class_checker->getAbsoluteClass();
|
||||
$class_checker->check($check_class_statements);
|
||||
}
|
||||
|
||||
@ -68,6 +71,7 @@ class FileChecker implements StatementsSource
|
||||
|
||||
$namespace_checker = new NamespaceChecker($stmt, $this);
|
||||
$this->_namespace_aliased_classes[$namespace_name] = $namespace_checker->check($check_classes, $check_class_statements);
|
||||
$this->_declared_classes = array_merge($namespace_checker->getDeclaredClasses());
|
||||
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Use_) {
|
||||
foreach ($stmt->uses as $use) {
|
||||
@ -81,33 +85,12 @@ class FileChecker implements StatementsSource
|
||||
|
||||
if ($leftover_stmts) {
|
||||
$statments_checker = new StatementsChecker($this);
|
||||
$existing_vars = [];
|
||||
$existing_vars_in_scope = [];
|
||||
$statments_checker->check($leftover_stmts, $existing_vars, $existing_vars_in_scope);
|
||||
$statments_checker->check($leftover_stmts, new Context());
|
||||
}
|
||||
|
||||
return $stmts;
|
||||
}
|
||||
|
||||
public function checkWithClass($class_name, $method_vars = [])
|
||||
{
|
||||
$stmts = self::getStatements($this->_real_file_name);
|
||||
|
||||
$class_method = new PhpParser\Node\Stmt\ClassMethod($class_name, ['stmts' => $stmts]);
|
||||
|
||||
if ($method_vars) {
|
||||
foreach ($method_vars as $method_var => $type) {
|
||||
$class_method->params[] = new PhpParser\Node\Param($method_var, null, $type);
|
||||
}
|
||||
}
|
||||
|
||||
$class = new PhpParser\Node\Stmt\Class_($class_name);
|
||||
|
||||
$class_checker = new ClassChecker($class, $this, $class_name);
|
||||
|
||||
(new ClassMethodChecker($class_method, $class_checker))->check();
|
||||
}
|
||||
|
||||
public static function getAbsoluteClassFromNameInFile($class, $namespace, $file_name)
|
||||
{
|
||||
if (isset(self::$_file_checkers[$file_name])) {
|
||||
@ -122,6 +105,33 @@ class FileChecker implements StatementsSource
|
||||
return ClassChecker::getAbsoluteClassFromString($class, $namespace, $aliased_classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the classes declared
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getDeclaredClasses()
|
||||
{
|
||||
return $this->_declared_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the classes declared in that file
|
||||
* @param string $file_name
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function getDeclaredClassesInFile($file_name)
|
||||
{
|
||||
if (isset(self::$_file_checkers[$file_name])) {
|
||||
$file_checker = self::$_file_checkers[$file_name];
|
||||
}
|
||||
else {
|
||||
$file_checker = new FileChecker($file_name);
|
||||
$file_checker->check(false);
|
||||
}
|
||||
|
||||
return $file_checker->getDeclaredClasses();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<\PhpParser\Node>
|
||||
*/
|
||||
|
@ -35,22 +35,22 @@ class FunctionChecker implements StatementsSource
|
||||
$this->_source = $source;
|
||||
}
|
||||
|
||||
public function check(&$vars_in_scope = [], &$vars_possibly_in_scope = [], $check_methods = true)
|
||||
public function check(Context $context, $check_methods = true)
|
||||
{
|
||||
if ($this->_function->stmts) {
|
||||
if ($this instanceof ClassMethodChecker) {
|
||||
if (ClassChecker::getThisClass()) {
|
||||
$hash = $this->getMethodId() . json_encode([$vars_in_scope, $vars_possibly_in_scope]);
|
||||
$hash = $this->getMethodId() . json_encode([$context->vars_in_scope, $context->vars_possibly_in_scope]);
|
||||
|
||||
// if we know that the function has no effects on vars, we don't bother rechecking
|
||||
if (isset(self::$_no_effects_hashes[$hash])) {
|
||||
list($vars_in_scope, $vars_possibly_in_scope) = self::$_no_effects_hashes[$hash];
|
||||
list($context->vars_in_scope, $context->vars_possibly_in_scope) = self::$_no_effects_hashes[$hash];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$vars_in_scope['this'] = new Type\Union([new Type\Atomic($this->_absolute_class)]);
|
||||
$context->vars_in_scope['this'] = new Type\Union([new Type\Atomic($this->_absolute_class)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ class FunctionChecker implements StatementsSource
|
||||
|
||||
foreach ($this->_function->params as $param) {
|
||||
if ($param->type) {
|
||||
if (is_object($param->type)) {
|
||||
if ($param->type instanceof PhpParser\Node\Name) {
|
||||
if (!in_array($param->type->parts[0], ['self', 'parent'])) {
|
||||
ClassChecker::checkClassName($param->type, $this->_namespace, $this->_aliased_classes, $this->_file_name);
|
||||
}
|
||||
@ -70,54 +70,59 @@ class FunctionChecker implements StatementsSource
|
||||
$param->default->name instanceof PhpParser\Node\Name &&
|
||||
$param->default->name->parts = ['null'];
|
||||
|
||||
if ($param->type && is_object($param->type)) {
|
||||
$param_class = $param->type->parts === ['self'] ?
|
||||
$this->_absolute_class :
|
||||
ClassChecker::getAbsoluteClassFromName($param->type, $this->_namespace, $this->_aliased_classes);
|
||||
|
||||
$param_type = new Type\Union([new Type\Atomic($param_class)]);
|
||||
|
||||
if ($is_nullable) {
|
||||
$param_type->types['null'] = Type::getNull(false);
|
||||
if ($param->type) {
|
||||
if ($param->type instanceof Type) {
|
||||
$context->vars_in_scope[$param->name] = clone $param->type;
|
||||
}
|
||||
else {
|
||||
if (is_string($param->type)) {
|
||||
$param_type_string = $param->type;
|
||||
}
|
||||
elseif ($param->type instanceof PhpParser\Node\Name) {
|
||||
$param_type_string = $param->type->parts === ['self']
|
||||
? $this->_absolute_class
|
||||
: ClassChecker::getAbsoluteClassFromName($param->type, $this->_namespace, $this->_aliased_classes);
|
||||
}
|
||||
|
||||
$vars_in_scope[$param->name] = $param_type;
|
||||
}
|
||||
elseif (is_string($param->type)) {
|
||||
$vars_in_scope[$param->name] = Type::parseString($param->type);
|
||||
if ($is_nullable) {
|
||||
$param_type_string .= '|null';
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$param->name] = Type::parseString($param_type_string);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$vars_in_scope[$param->name] = Type::getMixed();
|
||||
$context->vars_in_scope[$param->name] = Type::getMixed();
|
||||
}
|
||||
|
||||
$vars_possibly_in_scope[$param->name] = true;
|
||||
$context->vars_possibly_in_scope[$param->name] = true;
|
||||
$statements_checker->registerVariable($param->name, $param->getLine());
|
||||
}
|
||||
|
||||
$statements_checker->check($this->_function->stmts, $vars_in_scope, $vars_possibly_in_scope);
|
||||
$statements_checker->check($this->_function->stmts, $context);
|
||||
|
||||
if (isset($this->_return_vars_in_scope[''])) {
|
||||
$vars_in_scope = TypeChecker::combineKeyedTypes($vars_in_scope, $this->_return_vars_in_scope['']);
|
||||
$context->vars_in_scope = TypeChecker::combineKeyedTypes($context->vars_in_scope, $this->_return_vars_in_scope['']);
|
||||
}
|
||||
|
||||
if (isset($this->_return_vars_possibly_in_scope[''])) {
|
||||
$vars_possibly_in_scope = array_merge($vars_possibly_in_scope, $this->_return_vars_possibly_in_scope['']);
|
||||
$context->vars_possibly_in_scope = array_merge($context->vars_possibly_in_scope, $this->_return_vars_possibly_in_scope['']);
|
||||
}
|
||||
|
||||
foreach ($vars_in_scope as $var => $type) {
|
||||
foreach ($context->vars_in_scope as $var => $type) {
|
||||
if (strpos($var, 'this->') !== 0) {
|
||||
unset($vars_in_scope[$var]);
|
||||
unset($context->vars_in_scope[$var]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($vars_possibly_in_scope as $var => $type) {
|
||||
foreach ($context->vars_possibly_in_scope as $var => $type) {
|
||||
if (strpos($var, 'this->') !== 0) {
|
||||
unset($vars_possibly_in_scope[$var]);
|
||||
unset($context->vars_possibly_in_scope[$var]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ClassChecker::getThisClass() && $this instanceof ClassMethodChecker) {
|
||||
self::$_no_effects_hashes[$hash] = [$vars_in_scope, $vars_possibly_in_scope];
|
||||
self::$_no_effects_hashes[$hash] = [$context->vars_in_scope, $context->vars_possibly_in_scope];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,23 +130,23 @@ class FunctionChecker implements StatementsSource
|
||||
/**
|
||||
* Adds return types for the given function
|
||||
* @param string $return_type
|
||||
* @param array<Type> $vars_in_scope
|
||||
* @param array<bool> $vars_possibly_in_scope
|
||||
* @param array<Type> $context->vars_in_scope
|
||||
* @param array<bool> $context->vars_possibly_in_scope
|
||||
*/
|
||||
public function addReturnTypes($return_type, $vars_in_scope, $vars_possibly_in_scope)
|
||||
public function addReturnTypes($return_type, Context $context)
|
||||
{
|
||||
if (isset($this->_return_vars_in_scope[$return_type])) {
|
||||
$this->_return_vars_in_scope[$return_type] = TypeChecker::combineKeyedTypes($vars_in_scope, $this->_return_vars_in_scope[$return_type]);
|
||||
$this->_return_vars_in_scope[$return_type] = TypeChecker::combineKeyedTypes($context->vars_in_scope, $this->_return_vars_in_scope[$return_type]);
|
||||
}
|
||||
else {
|
||||
$this->_return_vars_in_scope[$return_type] = $vars_in_scope;
|
||||
$this->_return_vars_in_scope[$return_type] = $context->vars_in_scope;
|
||||
}
|
||||
|
||||
if (isset($this->_return_vars_possibly_in_scope[$return_type])) {
|
||||
$this->_return_vars_possibly_in_scope[$return_type] = array_merge($vars_possibly_in_scope, $this->_return_vars_possibly_in_scope[$return_type]);
|
||||
$this->_return_vars_possibly_in_scope[$return_type] = array_merge($context->vars_possibly_in_scope, $this->_return_vars_possibly_in_scope[$return_type]);
|
||||
}
|
||||
else {
|
||||
$this->_return_vars_possibly_in_scope[$return_type] = $vars_possibly_in_scope;
|
||||
$this->_return_vars_possibly_in_scope[$return_type] = $context->vars_possibly_in_scope;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
namespace CodeInspector\Issue;
|
||||
|
||||
class InvalidArrayAssignment extends CodeArray
|
||||
class InvalidArrayAssignment extends CodeError
|
||||
{
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class NamespaceChecker implements StatementsSource
|
||||
{
|
||||
protected $_namespace;
|
||||
protected $_namespace_name;
|
||||
protected $_contained_classes = [];
|
||||
protected $_declared_classes = [];
|
||||
protected $_aliased_classes = [];
|
||||
protected $_file_name;
|
||||
|
||||
@ -26,7 +26,7 @@ class NamespaceChecker implements StatementsSource
|
||||
foreach ($this->_namespace->stmts as $stmt) {
|
||||
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
||||
$absolute_class = ClassChecker::getAbsoluteClassFromString($stmt->name, $this->_namespace_name, []);
|
||||
$this->_contained_classes[$absolute_class] = 1;
|
||||
$this->_declared_classes[$absolute_class] = 1;
|
||||
|
||||
if ($check_classes) {
|
||||
$class_checker = ClassChecker::getClassCheckerFromClass($absolute_class) ?: new ClassChecker($stmt, $this, $absolute_class);
|
||||
@ -62,9 +62,18 @@ class NamespaceChecker implements StatementsSource
|
||||
return $this->_aliased_classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of the classes declared
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getDeclaredClasses()
|
||||
{
|
||||
return array_keys($this->_declared_classes);
|
||||
}
|
||||
|
||||
public function containsClass($class_name)
|
||||
{
|
||||
return isset($this->_contained_classes[$class_name]);
|
||||
return isset($this->_declared_classes[$class_name]);
|
||||
}
|
||||
|
||||
public function getNamespace()
|
||||
|
@ -14,7 +14,7 @@ abstract class Plugin
|
||||
* @param string $file_name
|
||||
* @return null|false
|
||||
*/
|
||||
public function checkExpression(PhpParser\Node\Expr $stmt, array &$vars_in_scope, array &$vars_possibly_in_scope, $file_name)
|
||||
public function checkExpression(PhpParser\Node\Expr $stmt, Context $context, $file_name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -27,7 +27,7 @@ abstract class Plugin
|
||||
* @param string $file_name
|
||||
* @return null|false
|
||||
*/
|
||||
public function checkStatement(PhpParser\Node $stmt, array &$vars_in_scope, array &$vars_possibly_in_scope, $file_name)
|
||||
public function checkStatement(PhpParser\Node $stmt, Context $context, $file_name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ class ProjectChecker
|
||||
$config = Config::getInstance();
|
||||
|
||||
$file_extensions = $config->getFileExtensions();
|
||||
$filetype_handlers = $config->getFiletypeHandlers();
|
||||
$base_dir = $config->getBaseDir();
|
||||
|
||||
/** @var RecursiveDirectoryIterator */
|
||||
@ -29,21 +30,50 @@ class ProjectChecker
|
||||
|
||||
while ($iterator->valid()) {
|
||||
if (!$iterator->isDot()) {
|
||||
if (in_array($iterator->getExtension(), $file_extensions)) {
|
||||
$files[] = $iterator->getRealPath();
|
||||
$extension = $iterator->getExtension();
|
||||
if (in_array($extension, $file_extensions)) {
|
||||
$file_name = $iterator->getRealPath();
|
||||
|
||||
if ($debug) {
|
||||
echo 'Checking ' . $file_name . PHP_EOL;
|
||||
}
|
||||
|
||||
if (isset($filetype_handlers[$extension])) {
|
||||
/** @var FileChecker */
|
||||
$file_checker = new $filetype_handlers[$extension]($file_name);
|
||||
}
|
||||
else {
|
||||
$file_checker = new FileChecker($file_name);
|
||||
}
|
||||
|
||||
$file_checker->check(true);
|
||||
}
|
||||
}
|
||||
|
||||
$iterator->next();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($files as $file_name) {
|
||||
if ($debug) {
|
||||
echo 'Checking ' . $file_name . PHP_EOL;
|
||||
}
|
||||
|
||||
$file_checker = new FileChecker($file_name);
|
||||
$file_checker->check(true);
|
||||
public static function checkFile($file_name, $debug = false)
|
||||
{
|
||||
if ($debug) {
|
||||
echo 'Checking ' . $file_name . PHP_EOL;
|
||||
}
|
||||
|
||||
$config = Config::getInstance();
|
||||
|
||||
$extension = array_pop(explode('.', $file_name));
|
||||
|
||||
$filetype_handlers = $config->getFiletypeHandlers();
|
||||
|
||||
if (isset($filetype_handlers[$extension])) {
|
||||
/** @var FileChecker */
|
||||
$file_checker = new $filetype_handlers[$extension]($file_name);
|
||||
}
|
||||
else {
|
||||
$file_checker = new FileChecker($file_name);
|
||||
}
|
||||
|
||||
$file_checker->check(true);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -40,19 +40,35 @@ class Union extends Type
|
||||
);
|
||||
}
|
||||
|
||||
public function removeType($type_string) {
|
||||
public function removeType($type_string)
|
||||
{
|
||||
unset($this->types[$type_string]);
|
||||
}
|
||||
|
||||
public function hasType($type_string) {
|
||||
public function hasType($type_string)
|
||||
{
|
||||
return isset($this->types[$type_string]);
|
||||
}
|
||||
|
||||
public function removeObjects() {
|
||||
public function removeObjects()
|
||||
{
|
||||
foreach ($this->types as $key => $type) {
|
||||
if ($key[0] === strtoupper($key[0])) {
|
||||
unset($this->types[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function substitute(Union $old_type, Union $new_type = null)
|
||||
{
|
||||
foreach ($old_type->types as $old_type_part) {
|
||||
$this->removeType($old_type_part->value);
|
||||
}
|
||||
|
||||
if ($new_type) {
|
||||
foreach ($new_type->types as $key => $new_type_part) {
|
||||
$this->types[$key] = $new_type_part;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user