1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-27 12:55:26 +01:00
psalm/examples/TemplateChecker.php

160 lines
4.8 KiB
PHP
Raw Normal View History

2016-10-30 04:07:13 +01:00
<?php
namespace Psalm\Example\Template;
use Psalm;
use Psalm\Checker\ClassChecker;
use Psalm\Checker\ClassLikeChecker;
2016-11-02 07:29:00 +01:00
use Psalm\Checker\CommentChecker;
2016-10-30 04:07:13 +01:00
use Psalm\Checker\FileChecker;
use Psalm\Checker\MethodChecker;
use Psalm\Context;
use Psalm\Type;
use PhpParser;
class TemplateChecker extends Psalm\Checker\FileChecker
{
const THIS_CLASS = 'Psalm\\Example\\Template\\Base';
2016-11-02 07:29:00 +01:00
/**
* @param bool $check_classes
* @param bool $check_class_statements
* @param Context|null $file_context
* @param bool $cache
2016-11-21 21:51:38 +01:00
* @param bool $update_docblocks
2016-11-02 07:29:00 +01:00
* @return false|null
*/
public function check(
$check_classes = true,
$check_class_statements = true,
Context $file_context = null,
2016-11-21 21:51:38 +01:00
$cache = true,
$update_docblocks = false
2016-11-04 01:51:56 +01:00
) {
2016-10-30 04:07:13 +01:00
$stmts = $this->getStatements();
if (empty($stmts)) {
2016-11-02 07:29:00 +01:00
return null;
2016-10-30 04:07:13 +01:00
}
$first_stmt = $stmts[0];
$this_params = null;
if (($first_stmt instanceof PhpParser\Node\Stmt\Nop) && ($doc_comment = $first_stmt->getDocComment())) {
$comment_block = CommentChecker::parseDocComment(trim($doc_comment->getText()));
if (isset($comment_block['specials']['variablesfrom'])) {
2016-11-21 21:51:38 +01:00
$variables_from = trim((string)$comment_block['specials']['variablesfrom'][0]);
2016-10-30 04:07:13 +01:00
$first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)?/';
$matches = [];
if (!preg_match($first_line_regex, $variables_from, $matches)) {
throw new \InvalidArgumentException('Could not interpret doc comment correctly');
}
2016-11-21 21:51:38 +01:00
$this_params = $this->checkMethod((string)$matches[1]);
2016-10-30 04:07:13 +01:00
if ($this_params === false) {
return false;
}
$this_params->vars_in_scope['$this'] = new Type\Union([new Type\Atomic(self::THIS_CLASS)]);
}
}
if (!$this_params) {
2016-11-21 21:51:38 +01:00
$this_params = new Context($this->file_name);
2016-10-30 04:07:13 +01:00
$this_params->check_variables = false;
$this_params->self = self::THIS_CLASS;
}
2016-11-02 07:29:00 +01:00
$this->checkWithViewClass($this_params);
return null;
2016-10-30 04:07:13 +01:00
}
2016-11-02 07:29:00 +01:00
/**
* @param string $method_id
2016-11-21 21:51:38 +01:00
* @return false|Context
2016-11-02 07:29:00 +01:00
*/
private function checkMethod($method_id)
2016-10-30 04:07:13 +01:00
{
$class = explode('::', $method_id)[0];
2016-11-21 21:51:38 +01:00
if (ClassLikeChecker::checkFullyQualifiedClassLikeName($class, $this->file_name, 1, []) === false) {
2016-10-30 04:07:13 +01:00
return false;
}
2016-11-21 21:51:38 +01:00
$this_context = new Context($this->file_name);
2016-10-30 04:07:13 +01:00
$this_context->self = $class;
$this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($class)]);
$constructor_id = $class . '::__construct';
// this is necessary to enable deep checks
ClassChecker::setThisClass($class);
// check the constructor
$constructor_method_checker = ClassChecker::getMethodChecker($constructor_id);
if ($constructor_method_checker->check($this_context) === false) {
ClassChecker::setThisClass(null);
return false;
}
$this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($class)]);
// check the actual method
$method_checker = ClassChecker::getMethodChecker($method_id);
if ($method_checker->check($this_context) === false) {
ClassChecker::setThisClass(null);
return false;
}
2016-11-21 21:51:38 +01:00
$view_context = new Context($this->file_name);
2016-10-30 04:07:13 +01:00
$view_context->self = self::THIS_CLASS;
// add all $this-> vars to scope
foreach ($this_context->vars_possibly_in_scope as $var => $type) {
$view_context->vars_in_scope[str_replace('$this->', '$', $var)] = Type::getMixed();
}
foreach ($this_context->vars_in_scope as $var => $type) {
$view_context->vars_in_scope[str_replace('$this->', '$', $var)] = $type;
}
ClassChecker::setThisClass(null);
return $view_context;
}
2016-11-02 07:29:00 +01:00
/**
* @param Context $context
* @return void
*/
protected function checkWithViewClass(Context $context)
2016-10-30 04:07:13 +01:00
{
$class_name = self::THIS_CLASS;
2016-11-21 21:51:38 +01:00
$class_like_checker = FileChecker::getClassLikeCheckerFromClass($class_name);
if (!$class_like_checker) {
return;
}
2016-10-30 04:07:13 +01:00
// check that class first
2016-11-21 21:51:38 +01:00
$class_like_checker->check(true);
2016-10-30 04:07:13 +01:00
$stmts = $this->getStatements();
$class_method = new PhpParser\Node\Stmt\ClassMethod($class_name, ['stmts' => $stmts]);
$class = new PhpParser\Node\Stmt\Class_($class_name);
$class_checker = new ClassChecker($class, $this, $class_name);
(new MethodChecker($class_method, $class_checker))->check($context);
}
}