Skip to content

Instantly share code, notes, and snippets.

@kocsismate
Last active March 1, 2024 13:12
Show Gist options
  • Save kocsismate/c0d10c820606dfe297f09374aa634df5 to your computer and use it in GitHub Desktop.
Save kocsismate/c0d10c820606dfe297f09374aa634df5 to your computer and use it in GitHub Desktop.
Analyze implicit nullable parameter types
<?php
error_reporting(E_ALL);
ini_set("memory_limit", "6G");
use PhpParser\Lexer\Emulative;
use PhpParser\Node;
use PhpParser\Node\Expr\ConstFetch;
use PhpParser\Node\NullableType;
use PhpParser\Node\Param;
use PhpParser\Node\UnionType;
use PhpParser\NodeVisitorAbstract;
require __DIR__ . '/vendor/autoload.php';
$lexer = new Emulative([
'usedAttributes' => [
'comments', 'startLine', 'endLine',
'startFilePos', 'endFilePos',
]
]);
$parser = new PhpParser\Parser\Php7($lexer);
$visitor = new class (__DIR__ . '/sources/') extends NodeVisitorAbstract {
public $path;
public $code;
public int $totalImplicitNullableParamTypes = 0;
public array $projectsUsingImplicitNullableParamTypes = [];
private string $sourceDir;
public function __construct(string $sourceDir)
{
$this->sourceDir = $sourceDir;
}
public function enterNode(Node $node) {
if (!$node instanceof Param) {
return;
}
if (!$node->type || !$node->default) {
return;
}
if ($node->default instanceof ConstFetch === false || $node->default->name->toLowerString() !== "null") {
return;
}
if ($node->type instanceof NullableType) {
return;
}
if ($node->type instanceof UnionType) {
foreach ($node->type->types as $type) {
if ($type instanceof NullableType) {
return;
}
}
}
if ($node->type instanceof Node\Identifier && $node->type->toLowerString() === "mixed") {
return;
}
$this->totalImplicitNullableParamTypes++;
$projectDirs = explode("/", str_replace($this->sourceDir, "", $this->path), 3);
$projectDir = $projectDirs[0] . "/" . $projectDirs[1];
if (!isset($this->projectsUsingImplicitNullableParamTypes[$projectDir])) {
$this->projectsUsingImplicitNullableParamTypes[$projectDir] = true;
}
//echo $this->path . ":" . $node->getStartLine() . "\n";
}
};
$traverser = new PhpParser\NodeTraverser;
$traverser->addVisitor($visitor);
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(__DIR__ . '/sources'),
RecursiveIteratorIterator::LEAVES_ONLY
);
$i = 0;
foreach ($it as $f) {
$path = $f->getPathName();
if (!preg_match('/\.php$/', $path)) {
continue;
}
if (++$i % 1000 == 0) {
echo $i . "\n";
}
$code = file_get_contents($path);
try {
$stmts = $parser->parse($code);
} catch (PhpParser\Error $e) {
echo $path . "\n";
echo "Parse error: " . $e->getMessage() . "\n";
continue;
}
$visitor->path = $path;
$visitor->code = $code;
$traverser->traverse($stmts);
}
echo "Total implicit nullable parameter types: ", $visitor->totalImplicitNullableParamTypes, "\n";
echo "Total projects using implicit nullable parameter types: ", count($visitor->projectsUsingImplicitNullableParamTypes), "\n";
foreach ($visitor->projectsUsingImplicitNullableParamTypes as $project => $_) {
echo "$project\n";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment