Files
XC_VM/tools/phpstan/gen-constants-stub.php
T
Divarion-D 66ecbc8cbc fix(phpstan): fix real bugs (return contracts, arg/byref, stub types) 599→585
- constants stub: use mt_rand()-based exprs so PHPStan infers GENERAL types,
  not literal 0/'' — fixes false division-by-zero (PACKET_SIZE) and
  foreach-over-false (str_split with len 0). Load stub via bootstrapFiles so
  result-cache invalidates on change.
- return contracts: explicit returns where a path fell through to null and
  violated the declared type:
  - StreamRepository::getById/getWatchFolder, GroupService::getById,
    getStream() → return false (declared array|false).
  - ServerRepository::getPublicURL → return '' when server missing (array→string).
  - MagService::resetSTB → return query() result (declared bool).
  - StreamUtils::getPlaylistSegments → explicit return null.
  - NetworkUtils::stopDownload → @return null corrected to @return void.
- PlexController: getPlexToken() called with 5 args but accepts 4 — dropped
  the dead 5th argument.
- DropboxClient::getMetaFromHeaders: array_shift() on an array_filter()
  expression (not a variable, by-ref error) — assign to a var first.
- WatchdogCommand: wrap numeric-string subtractions (nginx/proc-stat values)
  in floatval()/intval().
2026-06-23 20:02:46 +03:00

116 lines
4.2 KiB
PHP

<?php
/**
* Generates tools/phpstan/constants.stub.php by scanning every define() call in
* src/ and inferring a rough type for each constant. Run from the project root:
*
* php tools/phpstan/gen-constants-stub.php > tools/phpstan/constants.stub.php
*
* @package XC_VM
*/
$root = dirname(__DIR__, 2) . '/src';
$dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS));
$consts = [];
foreach ($dir as $f) {
if ($f->getExtension() !== 'php') {
continue;
}
$src = @file_get_contents($f->getPathname());
if ($src === false || strpos($src, 'define(') === false) {
continue;
}
$tokens = token_get_all($src);
$n = count($tokens);
for ($i = 0; $i < $n; $i++) {
$t = $tokens[$i];
if (!(is_array($t) && $t[0] === T_STRING && $t[1] === 'define')) {
continue;
}
$name = null;
$k = $i + 1;
for (; $k < $n && $k < $i + 40; $k++) {
$tk = $tokens[$k];
if (is_array($tk) && $tk[0] === T_CONSTANT_ENCAPSED_STRING) {
$name = trim($tk[1], "'\"");
break;
}
if ($tk === ')') {
break;
}
}
if (!$name || !preg_match('/^[A-Z_][A-Z0-9_]*$/', $name) || isset($consts[$name])) {
continue;
}
$hasIntLit = $hasStrLit = $hasConcat = $hasBool = false;
$isIntOnly = true;
$seen = false;
$d = 0;
for ($m = $k + 1; $m < $n && $m < $k + 80; $m++) {
$tm = $tokens[$m];
if ($tm === '(') { $d++; continue; }
if ($tm === ')') { if ($d === 0) break; $d--; continue; }
if ($tm === ',' && $d === 0) { $seen = true; continue; }
if (!$seen) { continue; }
if (is_array($tm)) {
if ($tm[0] === T_LNUMBER || $tm[0] === T_DNUMBER) { $hasIntLit = true; }
elseif ($tm[0] === T_CONSTANT_ENCAPSED_STRING) { $hasStrLit = true; $isIntOnly = false; }
elseif ($tm[0] === T_STRING && in_array(strtolower($tm[1]), ['true', 'false'], true)) { $hasBool = true; $isIntOnly = false; }
elseif ($tm[0] === T_WHITESPACE) { /* skip */ }
else { $isIntOnly = false; }
} else {
if ($tm === '.') { $hasConcat = true; $isIntOnly = false; }
else { $isIntOnly = false; }
}
}
if ($hasConcat || $hasStrLit) { $type = 'string'; }
elseif ($hasBool && !$hasIntLit) { $type = 'bool'; }
elseif ($hasIntLit && $isIntOnly) { $type = 'int'; }
else { $type = 'mixed'; }
$consts[$name] = $type;
}
}
// Explicit type overrides for constants whose value the heuristic cannot
// classify (defined from variables or function calls). Keeps the stub accurate
// and prevents false null/mixed positives at call sites.
$overrides = [
'HOST' => 'string', // trim(explode(':', HTTP_HOST)[0])
'PAGE_NAME' => 'string',
'PHP_ERRORS' => 'bool', // dev-mode flag
];
foreach ($overrides as $name => $type) {
if (isset($consts[$name])) {
$consts[$name] = $type;
}
}
ksort($consts);
echo "<?php\n\n";
echo "/**\n";
echo " * PHPStan constants stub for XC_VM (auto-generated by gen-constants-stub.php).\n";
echo " *\n";
echo " * The application defines these constants at RUNTIME inside methods, where\n";
echo " * PHPStan cannot see them via static scanning. Values are a cast of an unset\n";
echo " * env var so PHPStan gets the correct GENERAL type without narrowing to a\n";
echo " * literal (which would cause false always-true/false comparison reports).\n";
echo " *\n";
echo " * @package XC_VM\n";
echo " */\n\n";
foreach ($consts as $name => $type) {
// Use mt_rand()-based expressions so PHPStan infers a GENERAL type
// (int/string/bool) and never constant-folds to a literal (0/'') — a
// literal would create false positives like division-by-zero or
// always-false comparisons at call sites.
$expr = match ($type) {
'string' => "(string) mt_rand()",
'int' => "mt_rand()",
'bool' => "(bool) mt_rand()",
default => "json_decode((string) mt_rand(), true)",
};
echo "if (!defined('$name')) define('$name', $expr);\n";
}