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

200 lines
6.1 KiB
PHP
Raw Normal View History

2016-10-30 04:07:13 +01:00
<?php
2017-12-06 07:05:51 +01:00
namespace Psalm\Examples\Template;
use PhpParser;
2016-10-30 04:07:13 +01:00
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\MethodChecker;
2017-12-06 07:05:51 +01:00
use Psalm\Checker\StatementsChecker;
use Psalm\CodeLocation;
2016-10-30 04:07:13 +01:00
use Psalm\Context;
use Psalm\Type;
class TemplateChecker extends Psalm\Checker\FileChecker
{
2017-12-06 07:05:51 +01:00
const VIEW_CLASS = 'Your\\View\\Class';
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
public function scan()
{
2016-10-30 04:07:13 +01:00
$stmts = $this->getStatements();
if (empty($stmts)) {
2017-12-06 07:05:51 +01:00
return;
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())) {
2017-12-06 07:05:51 +01:00
2016-10-30 04:07:13 +01:00
$comment_block = CommentChecker::parseDocComment(trim($doc_comment->getText()));
if (isset($comment_block['specials']['variablesfrom'])) {
2017-12-06 07:05:51 +01:00
$variables_from = trim($comment_block['specials']['variablesfrom'][0]);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/';
2016-10-30 04:07:13 +01:00
$matches = [];
if (!preg_match($first_line_regex, $variables_from, $matches)) {
throw new \InvalidArgumentException('Could not interpret doc comment correctly');
}
2017-12-06 07:05:51 +01:00
/** @psalm-suppress MixedArgument */
list($fq_class_name, $method_name) = explode('::', $matches[1]);
$this->project_checker->queueClassLikeForScanning($fq_class_name, $this->file_path, true);
}
}
$this->project_checker->queueClassLikeForScanning(self::VIEW_CLASS, $this->file_path);
parent::scan();
}
public function analyze(Context $context = null, $update_docblocks = false)
{
$this->project_checker->enableCache();
$stmts = $this->getStatements();
if (empty($stmts)) {
return;
}
$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'])) {
$variables_from = trim($comment_block['specials']['variablesfrom'][0]);
$first_line_regex = '/([A-Za-z\\\0-9]+::[a-z_A-Z]+)(\s+weak)?/';
$matches = [];
if (!preg_match($first_line_regex, $variables_from, $matches)) {
throw new \InvalidArgumentException('Could not interpret doc comment correctly');
}
/** @psalm-suppress MixedArgument */
$this_params = $this->checkMethod($matches[1], $first_stmt);
2016-10-30 04:07:13 +01:00
if ($this_params === false) {
2017-12-06 07:05:51 +01:00
return;
2016-10-30 04:07:13 +01:00
}
2017-12-06 07:05:51 +01:00
$this_params->vars_in_scope['$this'] = new Type\Union([
new Type\Atomic\TNamedObject(self::VIEW_CLASS)
]);
2016-10-30 04:07:13 +01:00
}
}
if (!$this_params) {
2017-12-06 07:05:51 +01:00
$this_params = new Context();
2016-10-30 04:07:13 +01:00
$this_params->check_variables = false;
2017-12-06 07:05:51 +01:00
$this_params->self = self::VIEW_CLASS;
$this_params->vars_in_scope['$this'] = new Type\Union([
new Type\Atomic\TNamedObject(self::VIEW_CLASS)
]);
2016-10-30 04:07:13 +01:00
}
2016-11-02 07:29:00 +01:00
$this->checkWithViewClass($this_params);
2017-12-06 07:05:51 +01:00
$this->project_checker->disableCache();
2016-10-30 04:07:13 +01:00
}
2016-11-02 07:29:00 +01:00
/**
2017-12-06 07:05:51 +01:00
* @param string $method_id
* @param PhpParser\Node $stmt
* @return Context|false
2016-11-02 07:29:00 +01:00
*/
2017-12-06 07:05:51 +01:00
private function checkMethod($method_id, PhpParser\Node $stmt)
2016-10-30 04:07:13 +01:00
{
$class = explode('::', $method_id)[0];
2017-12-06 07:05:51 +01:00
if (ClassLikeChecker::checkFullyQualifiedClassLikeName(
$this->project_checker,
$class,
new CodeLocation($this, $stmt),
[],
true
) === false
) {
2016-10-30 04:07:13 +01:00
return false;
}
2017-12-06 07:05:51 +01:00
$this_context = new Context();
2016-10-30 04:07:13 +01:00
$this_context->self = $class;
2017-12-06 07:05:51 +01:00
$this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic\TNamedObject($class)]);
2016-10-30 04:07:13 +01:00
$constructor_id = $class . '::__construct';
2017-12-06 07:05:51 +01:00
$this->project_checker->getMethodMutations($constructor_id, $this_context);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$this_context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic\TNamedObject($class)]);
2016-10-30 04:07:13 +01:00
// check the actual method
2017-12-06 07:05:51 +01:00
$this->project_checker->getMethodMutations($method_id, $this_context);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$view_context = new Context();
$view_context->self = self::VIEW_CLASS;
2016-10-30 04:07:13 +01:00
// 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;
}
return $view_context;
}
2016-11-02 07:29:00 +01:00
/**
2017-12-06 07:05:51 +01:00
* @param Context $context
* @return void
2016-11-02 07:29:00 +01:00
*/
protected function checkWithViewClass(Context $context)
2016-10-30 04:07:13 +01:00
{
2017-12-06 07:05:51 +01:00
$stmts = $this->getStatements();
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$pseudo_method_stmts = [];
2016-11-21 21:51:38 +01:00
2017-12-06 07:05:51 +01:00
foreach ($stmts as $stmt) {
if ($stmt instanceof PhpParser\Node\Stmt\Use_) {
$this->visitUse($stmt);
} else {
$pseudo_method_stmts[] = $stmt;
}
2016-11-21 21:51:38 +01:00
}
2017-12-06 07:05:51 +01:00
$pseudo_method_name = preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->file_name);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$class_method = new PhpParser\Node\Stmt\ClassMethod($pseudo_method_name, ['stmts' => []]);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$class = new PhpParser\Node\Stmt\Class_(self::VIEW_CLASS);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$class_checker = new ClassChecker($class, $this, self::VIEW_CLASS);
$view_method_checker = new MethodChecker($class_method, $class_checker);
if (!$context->check_variables) {
$view_method_checker->addSuppressedIssue('UndefinedVariable');
}
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$statements_checker = new StatementsChecker($view_method_checker);
2016-10-30 04:07:13 +01:00
2017-12-06 07:05:51 +01:00
$statements_checker->analyze($pseudo_method_stmts, $context);
2016-10-30 04:07:13 +01:00
}
}