mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 12:24:49 +01:00
Allow setting globals in config
This commit is contained in:
parent
9442805763
commit
056e5a5b1e
12
config.xsd
12
config.xsd
@ -18,6 +18,7 @@
|
||||
<xs:element name="forbiddenFunctions" type="ExitFunctionsType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="issueHandlers" type="IssueHandlersType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="ignoreExceptions" type="ExceptionsType" minOccurs="0" maxOccurs="1" />
|
||||
<xs:element name="globals" type="GlobalsType" minOccurs="0" maxOccurs="1" />
|
||||
</xs:choice>
|
||||
|
||||
<xs:attribute name="name" type="xs:string" />
|
||||
@ -431,4 +432,15 @@
|
||||
|
||||
<xs:attribute name="errorLevel" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="GlobalsType">
|
||||
<xs:sequence>
|
||||
<xs:element name="var" maxOccurs="unbounded" type="IdentifierType" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="IdentifierType">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="type" type="xs:string" use="required" />
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
|
@ -346,6 +346,11 @@ class Config
|
||||
/** @var string|null */
|
||||
public $error_baseline = null;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $globals = [];
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
self::$instance = $this;
|
||||
@ -760,6 +765,13 @@ class Config
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config_xml->globals) && isset($config_xml->globals->var)) {
|
||||
/** @var \SimpleXMLElement $var */
|
||||
foreach ($config_xml->globals->var as $var) {
|
||||
$config->globals[(string) $var['name']] = (string) $var['type'];
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
@ -110,6 +110,20 @@ class VariableFetchAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($context->is_global && is_string($stmt->name)) {
|
||||
$var_name = '$' . $stmt->name;
|
||||
|
||||
if (!$context->hasVariable($var_name, $statements_analyzer)) {
|
||||
$type = $statements_analyzer->getGlobalType($stmt->name);
|
||||
if ($type) {
|
||||
$context->vars_in_scope[$var_name] = $type;
|
||||
$context->vars_possibly_in_scope[$var_name] = true;
|
||||
$stmt->inferredType = clone $type;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array(
|
||||
$stmt->name,
|
||||
[
|
||||
@ -140,27 +154,6 @@ class VariableFetchAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($context->is_global && ($stmt->name === 'argv' || $stmt->name === 'argc')) {
|
||||
$var_name = '$' . $stmt->name;
|
||||
|
||||
if (!$context->hasVariable($var_name, $statements_analyzer)) {
|
||||
if ($stmt->name === 'argv') {
|
||||
$context->vars_in_scope[$var_name] = new Type\Union([
|
||||
new Type\Atomic\TArray([
|
||||
Type::getInt(),
|
||||
Type::getString(),
|
||||
]),
|
||||
]);
|
||||
} else {
|
||||
$context->vars_in_scope[$var_name] = Type::getInt();
|
||||
}
|
||||
}
|
||||
|
||||
$context->vars_possibly_in_scope[$var_name] = true;
|
||||
$stmt->inferredType = clone $context->vars_in_scope[$var_name];
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($stmt->name)) {
|
||||
return ExpressionAnalyzer::analyze($statements_analyzer, $stmt->name, $context);
|
||||
}
|
||||
|
@ -1530,4 +1530,26 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
|
||||
return $const_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type\Union|null
|
||||
*/
|
||||
public function getGlobalType(string $name)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
if (isset($config->globals[$name])) {
|
||||
return Type::parseString($config->globals[$name]);
|
||||
}
|
||||
|
||||
if ($name === 'argv') {
|
||||
return new Type\Union([
|
||||
new Type\Atomic\TArray([Type::getInt(), Type::getString()]),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($name === 'argc') {
|
||||
return Type::getInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1117,4 +1117,76 @@ class ConfigTest extends TestCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testGlobals()
|
||||
{
|
||||
$this->project_analyzer = $this->getProjectAnalyzerWithConfig(
|
||||
TestConfig::loadFromXML(
|
||||
dirname(__DIR__),
|
||||
'<?xml version="1.0"?>
|
||||
<psalm>
|
||||
<globals>
|
||||
<var name="glob1" type="string" />
|
||||
<var name="glob2" type="array{str:string}" />
|
||||
<var name="glob3" type="ns\Clazz" />
|
||||
<var name="glob4" type="string|null" />
|
||||
</globals>
|
||||
</psalm>'
|
||||
)
|
||||
);
|
||||
|
||||
$file_path = getcwd() . '/src/somefile.php';
|
||||
|
||||
$this->addFile(
|
||||
$file_path,
|
||||
'<?php
|
||||
namespace {
|
||||
ord($glob1);
|
||||
ord($glob2["str"]);
|
||||
$glob3->func();
|
||||
|
||||
assert($glob4 !== null);
|
||||
ord($glob4);
|
||||
|
||||
function example1(): void {
|
||||
global $glob1, $glob2, $glob3, $glob4;
|
||||
ord($glob1);
|
||||
ord($glob2["str"]);
|
||||
$glob3->func();
|
||||
ord($glob4);
|
||||
}
|
||||
|
||||
$glob1 = 0;
|
||||
error_reporting($glob1);
|
||||
|
||||
function example2(): void {
|
||||
global $glob1, $glob2, $glob3;
|
||||
error_reporting($glob1);
|
||||
ord($glob2["str"]);
|
||||
$glob3->func();
|
||||
}
|
||||
}
|
||||
namespace ns {
|
||||
ord($glob1);
|
||||
ord($glob2["str"]);
|
||||
$glob3->func();
|
||||
|
||||
class Clazz {
|
||||
public function func(): void {}
|
||||
}
|
||||
|
||||
function example3(): void {
|
||||
global $glob1, $glob2, $glob3;
|
||||
ord($glob1);
|
||||
ord($glob2["str"]);
|
||||
$glob3->func();
|
||||
}
|
||||
}'
|
||||
);
|
||||
|
||||
$this->analyzeFile($file_path, new Context());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user