mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Add code
This commit is contained in:
parent
33f39d8b5a
commit
f086e105b8
97
lib/ClassChecker.php
Normal file
97
lib/ClassChecker.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Vimeo\CodeInspector;
|
||||||
|
|
||||||
|
use \PhpParser;
|
||||||
|
use \PhpParser\Error;
|
||||||
|
use \PhpParser\ParserFactory;
|
||||||
|
|
||||||
|
class ClassChecker
|
||||||
|
{
|
||||||
|
protected $_file_name;
|
||||||
|
protected $_class;
|
||||||
|
protected $_namespace;
|
||||||
|
protected $_aliased_classes;
|
||||||
|
protected static $_existing_classes = [];
|
||||||
|
|
||||||
|
public function __construct(PhpParser\Node\Stmt\Class_ $class, $namespace, $aliased_classes, $file_name)
|
||||||
|
{
|
||||||
|
$this->_class = $class;
|
||||||
|
$this->_namespace = $namespace;
|
||||||
|
$this->_aliased_classes = $aliased_classes;
|
||||||
|
$this->_file_name = $file_name;
|
||||||
|
|
||||||
|
self::$_existing_classes[self::getAbsoluteClass($class->name, $this->_namespace, [])] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check()
|
||||||
|
{
|
||||||
|
if ($this->_class->extends instanceof PhpParser\Node\Name) {
|
||||||
|
self::checkClassName($this->_class->extends, $this->_namespace, $this->_aliased_classes, $this->_file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->_class->stmts as $stmt) {
|
||||||
|
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod) {
|
||||||
|
$method_checker = new ClassMethodChecker($stmt, $this->_namespace, $this->_aliased_classes, $this->_file_name, $this->_class->name, $this->_class->extends);
|
||||||
|
$method_checker->check();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function checkClassName(PhpParser\Node\Name $class_name, $namespace, array $aliased_classes, $file_name)
|
||||||
|
{
|
||||||
|
if ($class_name->parts[0] === 'static') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$absolute_class = self::getAbsoluteClassFromName($class_name, $namespace, $aliased_classes);
|
||||||
|
|
||||||
|
if (!isset(self::$_existing_classes[$absolute_class]) && !class_exists($absolute_class, true) && !interface_exists($absolute_class, true)) {
|
||||||
|
throw new CodeException('Class ' . $absolute_class . ' does not exist', $file_name, $class_name->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$_existing_classes[$absolute_class] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAbsoluteClassFromName(PhpParser\Node\Name $class_name, $namespace, array $aliased_classes)
|
||||||
|
{
|
||||||
|
if ($class_name instanceof PhpParser\Node\Name\FullyQualified) {
|
||||||
|
return '\\' . implode('\\', $class_name->parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::getAbsoluteClass(implode('\\', $class_name->parts), $namespace, $aliased_classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAbsoluteClass($class, $namespace, array $imported_namespaces) {
|
||||||
|
if ($class[0] === '\\') {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($class, '\\') !== false) {
|
||||||
|
$class_parts = explode('\\', $class);
|
||||||
|
$first_namespace = array_shift($class_parts);
|
||||||
|
|
||||||
|
if (isset($imported_namespaces[$first_namespace])) {
|
||||||
|
return self::_addSlash($imported_namespaces[$first_namespace] . '\\' . implode('\\', $class_parts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isset($imported_namespaces[$class])) {
|
||||||
|
return self::_addSlash($imported_namespaces[$class]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($namespace && substr($namespace, -1) !== '\\') {
|
||||||
|
$namespace .= '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::_addSlash($namespace . $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function _addSlash($class)
|
||||||
|
{
|
||||||
|
if ($class[0] === '\\') {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '\\' . $class;
|
||||||
|
}
|
||||||
|
}
|
9
lib/ClassMethodChecker.php
Normal file
9
lib/ClassMethodChecker.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Vimeo\CodeInspector;
|
||||||
|
|
||||||
|
use \PhpParser;
|
||||||
|
|
||||||
|
class ClassMethodChecker extends FunctionChecker
|
||||||
|
{
|
||||||
|
}
|
17
lib/ClosureChecker.php
Normal file
17
lib/ClosureChecker.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Vimeo\CodeInspector;
|
||||||
|
|
||||||
|
use \PhpParser;
|
||||||
|
|
||||||
|
class ClosureChecker extends FunctionChecker
|
||||||
|
{
|
||||||
|
public function check()
|
||||||
|
{
|
||||||
|
foreach ($this->_function->uses as $use) {
|
||||||
|
$this->_declared_variables[$use->var] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::check();
|
||||||
|
}
|
||||||
|
}
|
22
lib/CodeException.php
Normal file
22
lib/CodeException.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Vimeo\CodeInspector;
|
||||||
|
|
||||||
|
class CodeException extends \Exception
|
||||||
|
{
|
||||||
|
const CODE_EXCEPTION = 1;
|
||||||
|
public $line_number;
|
||||||
|
|
||||||
|
public function __construct($message, $file_name, $line_number, $code = self::CODE_EXCEPTION, \Exception $previous = null)
|
||||||
|
{
|
||||||
|
$this->line_number = $line_number;
|
||||||
|
$this->file_name = $file_name;
|
||||||
|
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSourceLine()
|
||||||
|
{
|
||||||
|
return $this->line_number;
|
||||||
|
}
|
||||||
|
}
|
100
lib/FileChecker.php
Normal file
100
lib/FileChecker.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Vimeo\CodeInspector;
|
||||||
|
|
||||||
|
use \PhpParser;
|
||||||
|
use \PhpParser\Error;
|
||||||
|
use \PhpParser\ParserFactory;
|
||||||
|
|
||||||
|
class FileChecker
|
||||||
|
{
|
||||||
|
protected $_file_name;
|
||||||
|
protected $_namespace;
|
||||||
|
protected $_aliased_classes = [];
|
||||||
|
|
||||||
|
protected static $_file_checkers = [];
|
||||||
|
|
||||||
|
public function __construct($file_name)
|
||||||
|
{
|
||||||
|
$this->_file_name = $file_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check($check_classes = true)
|
||||||
|
{
|
||||||
|
$contents = file_get_contents($this->_file_name);
|
||||||
|
|
||||||
|
$cache = \Application::getCache();
|
||||||
|
$cache_key = 'parser0' . md5($contents);
|
||||||
|
|
||||||
|
$stmts = $cache->get($cache_key);
|
||||||
|
|
||||||
|
if (!$stmts) {
|
||||||
|
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
|
||||||
|
|
||||||
|
$stmts = $parser->parse($contents);
|
||||||
|
|
||||||
|
$cache->set($cache_key, $stmts);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($stmts as $stmt) {
|
||||||
|
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
||||||
|
if ($check_classes) {
|
||||||
|
$this->_checkClass($stmt, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($stmt instanceof PhpParser\Node\Stmt\Namespace_) {
|
||||||
|
$this->_checkNamespace($stmt, $check_classes);
|
||||||
|
}
|
||||||
|
else if ($stmt instanceof PhpParser\Node\Stmt\Use_) {
|
||||||
|
foreach ($stmt->uses as $use) {
|
||||||
|
$this->_aliased_classes[$use->alias] = implode('\\', $use->name->parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$_file_checkers[$this->_file_name] = $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _checkNamespace(PhpParser\Node\Stmt\Namespace_ $namespace, $check_classes)
|
||||||
|
{
|
||||||
|
foreach ($namespace->stmts as $stmt) {
|
||||||
|
if ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
||||||
|
if ($namespace->name === null) {
|
||||||
|
throw new CodeException('Empty namespace', $this->_file_name, $stmt->getLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_namespace = implode('\\', $namespace->name->parts);
|
||||||
|
|
||||||
|
if ($check_classes) {
|
||||||
|
$this->_checkClass($stmt, $this->_namespace, $this->_aliased_classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($stmt instanceof PhpParser\Node\Stmt\Use_) {
|
||||||
|
foreach ($stmt->uses as $use) {
|
||||||
|
$this->_aliased_classes[$use->alias] = implode('\\', $use->name->parts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _checkClass(PhpParser\Node\Stmt\Class_ $class, $namespace = null)
|
||||||
|
{
|
||||||
|
(new ClassChecker($class, $namespace, $this->_aliased_classes, $this->_file_name))->check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAbsoluteClass($class)
|
||||||
|
{
|
||||||
|
return ClassChecker::getAbsoluteClass($class, $this->_namespace, $this->_aliased_classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getAbsoluteClassInFile($class, $file_name)
|
||||||
|
{
|
||||||
|
if (isset(self::$_file_checkers[$file_name])) {
|
||||||
|
return self::$_file_checkers[$file_name]->getAbsoluteClass($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_checker = new FileChecker($file_name);
|
||||||
|
$file_checker->check(false);
|
||||||
|
return $file_checker->getAbsoluteClass($class);
|
||||||
|
}
|
||||||
|
}
|
1114
lib/FunctionChecker.php
Normal file
1114
lib/FunctionChecker.php
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user