Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add allFunctionsGlobal and allConstantsGlobal options #11259

Merged
merged 14 commits into from
Feb 7, 2025
Merged
2 changes: 2 additions & 0 deletions config.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<xs:attribute name="ensureOverrideAttribute" type="xs:boolean" default="false" />
<xs:attribute name="findUnusedCode" type="xs:boolean" default="true" />
<xs:attribute name="disallowLiteralKeysOnUnshapedArrays" type="xs:boolean" default="false" />
<xs:attribute name="allFunctionsGlobal" type="xs:boolean" default="false" />
<xs:attribute name="allConstantsGlobal" type="xs:boolean" default="false" />
<xs:attribute name="findUnusedVariablesAndParams" type="xs:boolean" default="false" />
<xs:attribute name="findUnusedPsalmSuppress" type="xs:boolean" default="false" />
<xs:attribute name="findUnusedBaselineEntry" type="xs:boolean" default="true" />
Expand Down
17 changes: 17 additions & 0 deletions docs/running_psalm/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,23 @@ When `true`, Psalm will attempt to find all unused code (including unused variab
```
When `true`, Psalm will emit issues when using literal keys on unshaped arrays (useful to enforce usage of shaped arrays). Defaults to `false`.

#### allFunctionsGlobal
```xml
<psalm
allFunctionsGlobal="[bool]"
>
```
When `true`, Psalm will treat all functions declared in scanned files as available to all files, even if they aren't loaded by the Composer autoloader (useful for legacy codebases which make heavy use of `require`/`include`, instead of Composer's autoloader). Defaults to `false`.

#### allConstantsGlobal
```xml
<psalm
allConstantsGlobal="[bool]"
>
```

When `true`, Psalm will treat all constants declared in scanned files as available to all files, even if they aren't loaded by the Composer autoloader (useful for legacy codebases which make heavy use of `require`/`include`, instead of Composer's autoloader). Defaults to `false`.

#### findUnusedPsalmSuppress
```xml
<psalm
Expand Down
6 changes: 4 additions & 2 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.x-dev@897f124d9f0034b025e246809077e828f3537f7f">
<files psalm-version="6.x-dev@d5ee11cc5a050760e4ac0b0801f43c3780a66fda">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset>
<code><![CDATA[$comment_block->tags['variablesfrom'][0]]]></code>
Expand Down Expand Up @@ -61,6 +61,9 @@
<PossiblyNullReference>
<code><![CDATA[addAttribute]]></code>
</PossiblyNullReference>
<PossiblyUnusedMethod>
<code><![CDATA[hasStubFile]]></code>
</PossiblyUnusedMethod>
<PropertyTypeCoercion>
<code><![CDATA[$this]]></code>
</PropertyTypeCoercion>
Expand Down Expand Up @@ -1263,7 +1266,6 @@
<RiskyTruthyFalsyComparison>
<code><![CDATA[$composer_file_path]]></code>
<code><![CDATA[$function_storage->cased_name]]></code>
<code><![CDATA[$function_storage->cased_name]]></code>
</RiskyTruthyFalsyComparison>
</file>
<file src="src/Psalm/Internal/Codebase/TaintFlowGraph.php">
Expand Down
2 changes: 2 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
xsi:schemaLocation="https://getpsalm.org/schema/config config.xsd"
limitMethodComplexity="true"
errorBaseline="psalm-baseline.xml"
allFunctionsGlobal="true"
allConstantsGlobal="true"
findUnusedPsalmSuppress="true"
findUnusedBaselineEntry="true"
findUnusedIssueHandlerSuppression="true"
Expand Down
12 changes: 12 additions & 0 deletions src/Psalm/Codebase.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ final class Codebase
*/
public bool $register_stub_files = false;

public bool $all_functions_global = false;

public bool $all_constants_global = false;

public bool $find_unused_variables = false;

public Scanner $scanner;
Expand Down Expand Up @@ -585,6 +589,14 @@ public function getStubbedConstantType(string $const_id): ?Union
return self::$stubbed_constants[$const_id] ?? null;
}

/**
* @param array<string, Union> $stubs
*/
public function addGlobalConstantTypes(array $stubs): void
{
self::$stubbed_constants += $stubs;
}

/**
* @return array<string, Union>
*/
Expand Down
14 changes: 14 additions & 0 deletions src/Psalm/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ final class Config

public bool $literal_array_key_check = false;

public bool $all_functions_global = false;

public bool $all_constants_global = false;

public int $max_graph_size = 200;

public int $max_avg_path_length = 70;
Expand Down Expand Up @@ -1111,6 +1115,16 @@ private static function fromXmlAndPaths(
$config->literal_array_key_check = $attribute_text === 'true' || $attribute_text === '1';
}

if (isset($config_xml['allFunctionsGlobal'])) {
$attribute_text = (string) $config_xml['allFunctionsGlobal'];
$config->all_functions_global = $attribute_text === 'true' || $attribute_text === '1';
}

if (isset($config_xml['allConstantsGlobal'])) {
$attribute_text = (string) $config_xml['allConstantsGlobal'];
$config->all_constants_global = $attribute_text === 'true' || $attribute_text === '1';
}

if (isset($config_xml['findUnusedVariablesAndParams'])) {
$attribute_text = (string) $config_xml['findUnusedVariablesAndParams'];
$config->find_unused_variables = $attribute_text === 'true' || $attribute_text === '1';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,8 @@ public static function checkFunctionExists(
} else {
IssueBuffer::maybeAdd(
new UndefinedFunction(
'Function ' . $cased_function_id . ' does not exist',
'Function ' . $cased_function_id . ' does not exist'
.', consider enabling the allFunctionsGlobal config option if scanning legacy codebases',
$code_location,
$function_id,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public static function analyze(
} elseif ($context->check_consts) {
IssueBuffer::maybeAdd(
new UndefinedConstant(
'Const ' . $const_name . ' is not defined',
'Const ' . $const_name . ' is not defined'.
', consider enabling the allConstantsGlobal config option if scanning legacy codebases',
new CodeLocation($statements_analyzer->getSource(), $stmt),
),
$statements_analyzer->getSuppressedIssues(),
Expand Down Expand Up @@ -151,10 +152,12 @@ public static function getGlobalConstType(
|| array_key_exists($const_name, $predefined_constants)
) {
switch ($const_name) {
case 'PHP_VERSION':
case 'DIRECTORY_SEPARATOR':
case 'PATH_SEPARATOR':
case 'PHP_EOL':
return Type::getSingleLetter();

case 'PHP_VERSION':
return Type::getNonEmptyString();

case 'PEAR_EXTENSION_DIR':
Expand Down
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Cli/Psalm.php
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,8 @@ private static function configureProjectAnalyzer(
if ($config->literal_array_key_check) {
$project_analyzer->getCodebase()->literal_array_key_check = true;
}
$project_analyzer->getCodebase()->all_constants_global = $config->all_constants_global;
$project_analyzer->getCodebase()->all_functions_global = $config->all_functions_global;

if ($config->run_taint_analysis || $run_taint_analysis) {
$project_analyzer->trackTaintedInputs();
Expand Down
10 changes: 9 additions & 1 deletion src/Psalm/Internal/Codebase/Functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,21 @@ public function addGlobalFunction(string $function_id, FunctionStorage $storage)
self::$stubbed_functions[strtolower($function_id)] = $storage;
}

/**
* @param array<lowercase-string, FunctionStorage> $stubs
*/
public function addGlobalFunctions(array $stubs): void
{
self::$stubbed_functions += $stubs;
}

public function hasStubbedFunction(string $function_id): bool
{
return isset(self::$stubbed_functions[strtolower($function_id)]);
}

/**
* @return array<string, FunctionStorage>
* @return array<lowercase-string, FunctionStorage>
*/
public function getAllStubbedFunctions(): array
{
Expand Down
39 changes: 15 additions & 24 deletions src/Psalm/Internal/Codebase/Scanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
use Psalm\Progress\Progress;
use Psalm\Storage\ClassLikeStorage;
use Psalm\Storage\FileStorage;
use Psalm\Storage\FunctionStorage;
use Psalm\Type;
use Psalm\Type\Union;
use ReflectionClass;
use Throwable;
use UnexpectedValueException;
Expand Down Expand Up @@ -78,7 +80,9 @@
* classlike_storage:array<string, ClassLikeStorage>,
* file_storage:array<lowercase-string, FileStorage>,
* new_file_content_hashes: array<string, string>,
* taint_data: ?TaintFlowGraph
* taint_data: ?TaintFlowGraph,
* global_constants: array<string, Union>,
* global_functions: array<lowercase-string, FunctionStorage>
* }
*/

Expand Down Expand Up @@ -352,6 +356,9 @@ private function scanFilePaths(int $pool_size): bool
$pool_data['new_file_content_hashes'],
);
}

$this->codebase->addGlobalConstantTypes($pool_data['global_constants']);
$this->codebase->functions->addGlobalFunctions($pool_data['global_functions']);
}
} else {
foreach ($files_to_scan as $file_path => $_) {
Expand All @@ -364,27 +371,6 @@ private function scanFilePaths(int $pool_size): bool
$this->codebase->statements_provider->parser_cache_provider->saveFileContentHashes();
}

foreach ($files_to_scan as $scanned_file) {
if ($this->config->hasStubFile($scanned_file)) {
$file_storage = $this->file_storage_provider->get($scanned_file);

foreach ($file_storage->functions as $function_storage) {
if ($function_storage->cased_name
&& !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name)
) {
$this->codebase->functions->addGlobalFunction(
$function_storage->cased_name,
$function_storage,
);
}
}

foreach ($file_storage->constants as $name => $type) {
$this->codebase->addGlobalConstantType($name, $type);
}
}
}

$this->file_reference_provider->addClassLikeFiles($this->classlike_files);

return true;
Expand Down Expand Up @@ -519,7 +505,9 @@ private function scanFile(
$this->queueClassLikeForScanning($fq_classlike_name, false, false);
}

if ($this->codebase->register_autoload_files) {
if ($this->codebase->register_autoload_files
|| $this->codebase->all_functions_global
) {
foreach ($file_storage->functions as $function_storage) {
if ($function_storage->cased_name
&& !$this->codebase->functions->hasStubbedFunction($function_storage->cased_name)
Expand All @@ -530,7 +518,10 @@ private function scanFile(
);
}
}

}
if ($this->codebase->register_autoload_files
|| $this->codebase->all_constants_global
) {
foreach ($file_storage->constants as $name => $type) {
$this->codebase->addGlobalConstantType($name, $type);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Psalm/Internal/Fork/ShutdownScannerTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public function run(Channel $channel, Cancellation $cancellation): mixed
? $statements_provider->parser_cache_provider->getNewFileContentHashes()
: [],
'taint_data' => $codebase->taint_flow_graph,
'global_constants' => $codebase->getAllStubbedConstants(),
'global_functions' => $codebase->functions->getAllStubbedFunctions(),
];
}
}
6 changes: 4 additions & 2 deletions src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,10 @@ private static function registerClassMapFunctionCall(
$file_storage->declaring_constants[$const_name] = $file_storage->file_path;
}

if (($codebase->register_stub_files || $codebase->register_autoload_files)
&& (!defined($const_name) || $const_type->isMixed())
if (($codebase->register_stub_files
|| $codebase->register_autoload_files
|| $codebase->all_constants_global
) && (!defined($const_name) || !$const_type->isMixed())
) {
$codebase->addGlobalConstantType($const_name, $const_type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,8 @@ public function start(
&& $function_id
&& $storage instanceof FunctionStorage
) {
if ($this->codebase->register_stub_files
if ($this->codebase->all_functions_global
|| $this->codebase->register_stub_files
|| ($this->codebase->register_autoload_files
&& !$this->codebase->functions->hasStubbedFunction($function_id))
) {
Expand Down Expand Up @@ -914,7 +915,10 @@ private function createStorageForFunctionLike(

$storage = $this->storage = new FunctionStorage();

if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) {
if ($this->codebase->register_stub_files
|| $this->codebase->register_autoload_files
|| $this->codebase->all_functions_global
) {
if (isset($this->file_storage->functions[$function_id])
&& ($this->codebase->register_stub_files
|| !$this->codebase->functions->hasStubbedFunction($function_id))
Expand Down
7 changes: 6 additions & 1 deletion src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
use UnexpectedValueException;

use function array_pop;
use function defined;
use function end;
use function explode;
use function in_array;
Expand Down Expand Up @@ -288,7 +289,11 @@ public function enterNode(PhpParser\Node $node): ?int

$fq_const_name = Type::getFQCLNFromString($const->name->name, $this->aliases);

if ($this->codebase->register_stub_files || $this->codebase->register_autoload_files) {
if (($this->codebase->register_stub_files
|| $this->codebase->register_autoload_files
|| $this->codebase->all_constants_global
) && (!defined($fq_const_name) || !$const_type->isMixed())
) {
$this->codebase->addGlobalConstantType($fq_const_name, $const_type);
}

Expand Down
Loading