mirror of
https://github.com/danog/psalm.git
synced 2025-01-23 06:11:25 +01:00
Merge pull request #9301 from weirdan/allow-var-docblock-tags-on-global
This commit is contained in:
commit
d9161c3439
@ -4,6 +4,9 @@ namespace Psalm\Internal\Analyzer;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\Aliases;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\CodeLocation\DocblockTypeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\DocComment;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Exception\IncorrectDocblockException;
|
||||
@ -13,12 +16,18 @@ use Psalm\Internal\Scanner\DocblockParser;
|
||||
use Psalm\Internal\Scanner\ParsedDocblock;
|
||||
use Psalm\Internal\Scanner\VarDocblockComment;
|
||||
use Psalm\Internal\Type\TypeAlias;
|
||||
use Psalm\Internal\Type\TypeExpander;
|
||||
use Psalm\Internal\Type\TypeParser;
|
||||
use Psalm\Internal\Type\TypeTokenizer;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\MissingDocblockType;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type\Union;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function preg_split;
|
||||
@ -381,4 +390,127 @@ class CommentAnalyzer
|
||||
|
||||
return [$type];
|
||||
}
|
||||
|
||||
/** @return list<VarDocblockComment> */
|
||||
public static function getVarComments(
|
||||
PhpParser\Comment\Doc $doc_comment,
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\Variable $var
|
||||
): array {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
$parsed_docblock = $statements_analyzer->getParsedDocblock();
|
||||
|
||||
if (!$parsed_docblock) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$var_comments = [];
|
||||
|
||||
try {
|
||||
$var_comments = $codebase->config->disable_var_parsing
|
||||
? []
|
||||
: self::arrayToDocblocks(
|
||||
$doc_comment,
|
||||
$parsed_docblock,
|
||||
$statements_analyzer->getSource(),
|
||||
$statements_analyzer->getSource()->getAliases(),
|
||||
$statements_analyzer->getSource()->getTemplateTypeMap(),
|
||||
);
|
||||
} catch (IncorrectDocblockException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingDocblockType(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var),
|
||||
),
|
||||
);
|
||||
} catch (DocblockParseException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer->getSource(), $var),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $var_comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<VarDocblockComment> $var_comments
|
||||
*/
|
||||
public static function populateVarTypesFromDocblock(
|
||||
array $var_comments,
|
||||
PhpParser\Node\Expr\Variable $var,
|
||||
Context $context,
|
||||
StatementsAnalyzer $statements_analyzer
|
||||
): ?Union {
|
||||
if (!is_string($var->name)) {
|
||||
return null;
|
||||
}
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
$comment_type = null;
|
||||
$var_id = '$' . $var->name;
|
||||
|
||||
foreach ($var_comments as $var_comment) {
|
||||
if (!$var_comment->type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$var_comment_type = TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$var_comment->type,
|
||||
$context->self,
|
||||
$context->self,
|
||||
$statements_analyzer->getParentFQCLN(),
|
||||
);
|
||||
|
||||
$var_comment_type = $var_comment_type->setFromDocblock();
|
||||
|
||||
/** @psalm-suppress UnusedMethodCall */
|
||||
$var_comment_type->check(
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $var),
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
);
|
||||
|
||||
if ($codebase->alter_code
|
||||
&& $var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$type_location = new DocblockTypeLocation(
|
||||
$statements_analyzer,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number,
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$var_comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$var_comment->var_id || $var_comment->var_id === $var_id) {
|
||||
$comment_type = $var_comment_type;
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$var_comment->var_id] = $var_comment_type;
|
||||
} catch (UnexpectedValueException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $comment_type;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace Psalm\Internal\Analyzer\Statements;
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
@ -43,10 +44,34 @@ class GlobalAnalyzer
|
||||
: null;
|
||||
|
||||
foreach ($stmt->vars as $var) {
|
||||
if ($var instanceof PhpParser\Node\Expr\Variable) {
|
||||
if (is_string($var->name)) {
|
||||
if (!$var instanceof PhpParser\Node\Expr\Variable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_string($var->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$var_id = '$' . $var->name;
|
||||
|
||||
$doc_comment = $stmt->getDocComment();
|
||||
$comment_type = null;
|
||||
|
||||
if ($doc_comment) {
|
||||
$var_comments = CommentAnalyzer::getVarComments($doc_comment, $statements_analyzer, $var);
|
||||
$comment_type = CommentAnalyzer::populateVarTypesFromDocblock(
|
||||
$var_comments,
|
||||
$var,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
);
|
||||
}
|
||||
|
||||
if ($comment_type) {
|
||||
$context->vars_in_scope[$var_id] = $comment_type;
|
||||
$context->vars_possibly_in_scope[$var_id] = true;
|
||||
$context->byref_constraints[$var_id] = new ReferenceConstraint($comment_type);
|
||||
} else {
|
||||
if ($var->name === 'argv' || $var->name === 'argc') {
|
||||
$context->vars_in_scope[$var_id] =
|
||||
VariableFetchAnalyzer::getGlobalType($var_id, $codebase->analysis_php_version_id);
|
||||
@ -63,6 +88,7 @@ class GlobalAnalyzer
|
||||
|
||||
$context->byref_constraints[$var_id] = new ReferenceConstraint();
|
||||
}
|
||||
}
|
||||
|
||||
$assignment_node = DataFlowNode::getForAssignment(
|
||||
$var_id,
|
||||
@ -95,5 +121,3 @@ class GlobalAnalyzer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,22 +4,15 @@ namespace Psalm\Internal\Analyzer\Statements;
|
||||
|
||||
use PhpParser;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\CodeLocation\DocblockTypeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Exception\DocblockParseException;
|
||||
use Psalm\Exception\IncorrectDocblockException;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\ReferenceConstraint;
|
||||
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
|
||||
use Psalm\Internal\Type\TypeExpander;
|
||||
use Psalm\Issue\ImpureStaticVariable;
|
||||
use Psalm\Issue\InvalidDocblock;
|
||||
use Psalm\Issue\MissingDocblockType;
|
||||
use Psalm\Issue\ReferenceConstraintViolation;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use UnexpectedValueException;
|
||||
|
||||
use function is_string;
|
||||
|
||||
@ -56,99 +49,19 @@ class StaticAnalyzer
|
||||
|
||||
$comment_type = null;
|
||||
|
||||
if ($doc_comment && ($parsed_docblock = $statements_analyzer->getParsedDocblock())) {
|
||||
$var_comments = [];
|
||||
|
||||
try {
|
||||
$var_comments = $codebase->config->disable_var_parsing
|
||||
? []
|
||||
: CommentAnalyzer::arrayToDocblocks(
|
||||
$doc_comment,
|
||||
$parsed_docblock,
|
||||
$statements_analyzer->getSource(),
|
||||
$statements_analyzer->getSource()->getAliases(),
|
||||
$statements_analyzer->getSource()->getTemplateTypeMap(),
|
||||
);
|
||||
} catch (IncorrectDocblockException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new MissingDocblockType(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var),
|
||||
),
|
||||
);
|
||||
} catch (DocblockParseException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer->getSource(), $var),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($var_comments as $var_comment) {
|
||||
if (!$var_comment->type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$var_comment_type = TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$var_comment->type,
|
||||
$context->self,
|
||||
$context->self,
|
||||
$statements_analyzer->getParentFQCLN(),
|
||||
);
|
||||
|
||||
$var_comment_type = $var_comment_type->setFromDocblock();
|
||||
|
||||
/** @psalm-suppress UnusedMethodCall */
|
||||
$var_comment_type->check(
|
||||
if ($doc_comment) {
|
||||
$var_comments = CommentAnalyzer::getVarComments($doc_comment, $statements_analyzer, $var->var);
|
||||
$comment_type = CommentAnalyzer::populateVarTypesFromDocblock(
|
||||
$var_comments,
|
||||
$var->var,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $var),
|
||||
$statements_analyzer->getSuppressedIssues(),
|
||||
);
|
||||
|
||||
if ($codebase->alter_code
|
||||
&& $var_comment->type_start
|
||||
&& $var_comment->type_end
|
||||
&& $var_comment->line_number
|
||||
) {
|
||||
$type_location = new DocblockTypeLocation(
|
||||
$statements_analyzer,
|
||||
$var_comment->type_start,
|
||||
$var_comment->type_end,
|
||||
$var_comment->line_number,
|
||||
);
|
||||
|
||||
$codebase->classlikes->handleDocblockTypeInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$var_comment_type,
|
||||
$type_location,
|
||||
$context->calling_method_id,
|
||||
);
|
||||
}
|
||||
|
||||
if (!$var_comment->var_id || $var_comment->var_id === $var_id) {
|
||||
$comment_type = $var_comment_type;
|
||||
continue;
|
||||
}
|
||||
|
||||
$context->vars_in_scope[$var_comment->var_id] = $var_comment_type;
|
||||
} catch (UnexpectedValueException $e) {
|
||||
IssueBuffer::maybeAdd(
|
||||
new InvalidDocblock(
|
||||
$e->getMessage(),
|
||||
new CodeLocation($statements_analyzer, $var),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($comment_type) {
|
||||
$context->byref_constraints[$var_id] = new ReferenceConstraint($comment_type);
|
||||
}
|
||||
}
|
||||
|
||||
if ($var->default) {
|
||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $var->default, $context) === false) {
|
||||
|
@ -1248,6 +1248,24 @@ class AnnotationTest extends TestCase
|
||||
}
|
||||
}',
|
||||
],
|
||||
'globalDocBlock' => [
|
||||
'code' => '<?php
|
||||
function f(): string {
|
||||
/** @var string $a */
|
||||
global $a;
|
||||
return $a;
|
||||
}',
|
||||
],
|
||||
'globalDocBlockInGlobalScope' => [
|
||||
'code' => '<?php
|
||||
/**
|
||||
* @psalm-suppress InvalidGlobal
|
||||
* @var string $a
|
||||
*/
|
||||
global $a;
|
||||
echo strlen($a);
|
||||
',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user