1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 12:24:49 +01:00

Track references on global variables (#5122)

* Track references on global variables

Add global type references to the type map, and fix up unused detection on global variables.

* Add null assertions

* PHPCS
This commit is contained in:
Joe Hoyle 2021-01-28 18:58:02 -05:00 committed by Daniil Gentili
parent 6953658961
commit 417704ac23
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
5 changed files with 79 additions and 2 deletions

View File

@ -21,6 +21,7 @@ use Psalm\Internal\Analyzer\ProjectAnalyzer;
use Psalm\Internal\Analyzer\NamespaceAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer;
use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer; use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator; use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Codebase\InternalCallMapHandler; use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\Provider\ClassLikeStorageProvider; use Psalm\Internal\Provider\ClassLikeStorageProvider;
@ -1004,6 +1005,13 @@ class Codebase
return '<?php ' . $function->getSignature(true); return '<?php ' . $function->getSignature(true);
} }
if (strpos($symbol, '$') === 0) {
$type = VariableFetchAnalyzer::getGlobalType($symbol);
if (!$type->isMixed()) {
return '<?php ' . $type;
}
}
try { try {
$storage = $this->classlike_storage_provider->get($symbol); $storage = $this->classlike_storage_provider->get($symbol);
return '<?php ' . ($storage->abstract ? 'abstract ' : '') . 'class ' . $storage->name; return '<?php ' . ($storage->abstract ? 'abstract ' : '') . 'class ' . $storage->name;

View File

@ -165,6 +165,12 @@ class VariableFetchAnalyzer
$context->vars_in_scope[$var_name] = clone $type; $context->vars_in_scope[$var_name] = clone $type;
$context->vars_possibly_in_scope[$var_name] = true; $context->vars_possibly_in_scope[$var_name] = true;
$codebase->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$stmt,
$var_name
);
return true; return true;
} }

View File

@ -5,6 +5,7 @@ use PhpParser;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer; use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer; use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\CodeLocation; use Psalm\CodeLocation;
use Psalm\Context; use Psalm\Context;
use Psalm\Issue\InvalidGlobal; use Psalm\Issue\InvalidGlobal;
@ -56,6 +57,23 @@ class GlobalAnalyzer
$context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint(); $context->byref_constraints[$var_id] = new \Psalm\Internal\ReferenceConstraint();
} }
$assignment_node = DataFlowNode::getForAssignment(
$var_id,
new CodeLocation($statements_analyzer, $var)
);
$context->vars_in_scope[$var_id]->parent_nodes = [
$assignment_node->id => $assignment_node,
];
$statements_analyzer->registerVariable(
$var_id,
new CodeLocation($statements_analyzer, $var),
$context->branch_point
);
$statements_analyzer->getCodebase()->analyzer->addNodeReference(
$statements_analyzer->getFilePath(),
$var,
$var_id
);
} }
} }
} }

View File

@ -39,6 +39,9 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
public function testSimpleSymbolLookup(): void public function testSimpleSymbolLookup(): void
{ {
$codebase = $this->project_analyzer->getCodebase();
$config = $codebase->config;
$config->globals['$my_global'] = 'string';
$this->addFile( $this->addFile(
'somefile.php', 'somefile.php',
'<?php '<?php
@ -68,7 +71,9 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
function qux(int $a, int $b) : int { function qux(int $a, int $b) : int {
return $a + $b; return $a + $b;
}' }
$_SERVER;'
); );
new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php'); new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php');
@ -83,7 +88,8 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
$this->assertSame('<?php BANANA', $codebase->getSymbolInformation('somefile.php', 'B\A::BANANA')); $this->assertSame('<?php BANANA', $codebase->getSymbolInformation('somefile.php', 'B\A::BANANA'));
$this->assertSame("<?php function B\baz(\n int \$a\n) : int", $codebase->getSymbolInformation('somefile.php', 'B\baz()')); $this->assertSame("<?php function B\baz(\n int \$a\n) : int", $codebase->getSymbolInformation('somefile.php', 'B\baz()'));
$this->assertSame("<?php function B\qux(\n int \$a,\n int \$b\n) : int", $codebase->getSymbolInformation('somefile.php', 'B\qux()')); $this->assertSame("<?php function B\qux(\n int \$a,\n int \$b\n) : int", $codebase->getSymbolInformation('somefile.php', 'B\qux()'));
$this->assertSame("<?php const B\APPLE string", $codebase->getSymbolInformation('somefile.php', 'B\APPLE')); $this->assertSame("<?php array<array-key, mixed>", $codebase->getSymbolInformation('somefile.php', '$_SERVER'));
$this->assertSame("<?php string", $codebase->getSymbolInformation('somefile.php', '$my_global'));
} }
public function testSimpleSymbolLookupGlobalConst(): void public function testSimpleSymbolLookupGlobalConst(): void
@ -279,6 +285,36 @@ class SymbolLookupTest extends \Psalm\Tests\TestCase
$this->assertSame('B\A::foo()', $symbol_at_position[0]); $this->assertSame('B\A::foo()', $symbol_at_position[0]);
} }
public function testGetSymbolPositionGlobalVariable(): void
{
$codebase = $this->project_analyzer->getCodebase();
$codebase->reportUnusedVariables();
$config = $codebase->config;
$config->throw_exception = false;
$config->globals['$my_global'] = 'string';
$this->addFile(
'somefile.php',
'<?php
function foo() : void {
global $my_global;
echo $my_global;
}'
);
$codebase->file_provider->openFile('somefile.php');
$codebase->scanFiles();
$this->analyzeFile('somefile.php', new Context());
$symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(2, 31));
$this->assertNotNull($symbol_at_position);
$this->assertSame('$my_global', $symbol_at_position[0]);
$symbol_at_position = $codebase->getReferenceAtPosition('somefile.php', new Position(3, 28));
$this->assertNotNull($symbol_at_position);
$this->assertSame('73-82:string', $symbol_at_position[0]);
}
public function testGetSymbolPositionNullableArg(): void public function testGetSymbolPositionNullableArg(): void
{ {
$codebase = $this->project_analyzer->getCodebase(); $codebase = $this->project_analyzer->getCodebase();

View File

@ -3114,6 +3114,15 @@ class UnusedVariableTest extends TestCase
};', };',
'error_message' => 'UnusedVariable', 'error_message' => 'UnusedVariable',
], ],
'globalVariableUsage' => [
'<?php
$a = "hello";
function example() : void {
global $a;
}
example();',
'error_message' => 'UnusedVariable',
],
]; ];
} }
} }