tool/make_stubs
#!/usr/bin/env php
<?php declare(strict_types=1);
use Phan\AST\TolerantASTConverter\Shim;
use Phan\CodeBase;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\GlobalConstant;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
require_once dirname(__DIR__) . '/src/requirements.php';
require_once dirname(__DIR__) . '/src/Phan/Bootstrap.php';
// If php-ast isn't loaded already, then load this file to generate equivalent
// class, constant, and function definitions.
Shim::load();
/**
* Generates PHP stubs for extensions that can be used in the autoload_internal_extension_signatures `.phan/config.php` setting.
* These are regular PHP files stubbing PHP modules, containing empty method implementations, etc.
*
* Configured stubs can be used in IDEs, analysis, etc. where extensions aren't installed or enabled (e.g. Xdebug)
* @phan-file-suppress PhanPluginDescriptionlessCommentOnPublicMethod, PhanPluginNoCommentOnPublicMethod
* @phan-file-suppress PhanPluginRemoveDebugAny
*/
class StubsGenerator
{
/**
* @return void (does not return)
*/
public static function printHelpAndExit(string $message = '', int $exit_code = 1) : void
{
if ($message !== '') {
echo "$message\n";
}
global $argv;
$prog_name = $argv[0];
echo <<<EOT
Usage: $prog_name [--opts]
-h, --help
Print this help message
-e, --extension extension_name
Print stubs for only the given PECL or built in extension (e.g. 'ast', 'pcntl')
-o, --output path/to/file
Save the stubs to a file instead of printing them to stdout.
EOT;
exit($exit_code);
}
/**
* The main function of the `make_stubs` script.
* See `make_stubs --help` for usage.
*/
public static function main() : void
{
$options = getopt('he:o:', ['help', 'extension:', 'output:']);
if (isset($options['h']) || isset($options['help'])) {
self::printHelpAndExit('', 0);
}
$code_base = require(dirname(__DIR__) . '/src/codebase.php');
$extension_name = $options['e'] ?? $options['extension'] ?? null;
$output_path = $options['o'] ?? $options['output'] ?? null;
if (is_string($extension_name)) {
$output = self::generateStubsForExtension($extension_name, $code_base);
} else {
$output = self::generateAllStubs($code_base);
}
if (is_string($output_path)) {
file_put_contents($output_path, $output);
fwrite(STDERR, "Saved stubs for $extension_name to $output_path\n");
} else {
echo $output;
}
}
private static function generateStubsForExtension(string $extension_name, CodeBase $code_base): string
{
$stub_collection = new StubCollection($code_base);
$reflection_extension = new ReflectionExtension($extension_name);
$extension_version = $reflection_extension->getVersion();
self::recordClassStubsForExtension($stub_collection, $reflection_extension, $code_base);
self::recordGlobalFunctionStubsForExtension($stub_collection, $reflection_extension, $code_base);
self::recordGlobalConstantStubsForExtension($stub_collection, $reflection_extension, $code_base);
$result = '';
$result .= "<" . "?php\n";
$result .= "// These stubs were generated by the phan stub generator.\n";
$result .= "// @phan-stub-for-extension $extension_name@$extension_version\n";
$result .= "\n";
$result .= $stub_collection->toString();
return $result;
}
private static function recordClassStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getClassNames() as $class_name) {
try {
$class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class_name);
} catch (Exception $e) {
// only possible if module info is wrong
fwrite(STDERR, "Failed to parse fqsen of class $class_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
fwrite(STDERR, "Failed to find class $class_fqsen\n");
continue;
}
$stub_collection->addClazz($code_base->getClassByFQSEN($class_fqsen));
}
}
private static function recordGlobalFunctionStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getFunctions() as $function_name => $unused_reflection_function) {
try {
$function_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($function_name);
} catch (Exception $e) {
// only possible if module info is wrong
fwrite(STDERR, "Failed to parse fqsen of function $function_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasFunctionWithFQSEN($function_fqsen)) {
fwrite(STDERR, "Failed to find function $function_fqsen\n");
continue;
}
$stub_collection->addFunc($code_base->getFunctionByFQSEN($function_fqsen));
}
}
private static function recordGlobalConstantStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getConstants() as $constant_name => $_) {
try {
$const_fqsen = FullyQualifiedGlobalConstantName::fromFullyQualifiedString($constant_name);
} catch (Exception $e) {
// only possible if module info is invalid
fwrite(STDERR, "Failed to parse fqsen of global constant $constant_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasGlobalConstantWithFQSEN($const_fqsen)) {
fwrite(STDERR, "Failed to find global constant $const_fqsen\n");
continue;
}
$stub_collection->addGlobalConstant($code_base->getGlobalConstantByFQSEN($const_fqsen));
}
}
public static function generateAllStubs(CodeBase $code_base): string
{
$code_base->eagerlyLoadAllSignatures();
$stub_collection = new StubCollection($code_base);
$class_map = $code_base->getInternalClassMap();
$result = '';
$result .= "<" . "?php\n";
$result .= "// These stubs were generated by the phan stub generator.\n";
foreach ($class_map as $class) {
$stub_collection->addClazz($class);
}
$function_map = $code_base->getFunctionMap();
foreach ($function_map as $function) {
if ($function->getFQSEN()->isAlternate()) {
continue;
}
$stub_collection->addFunc($function);
}
$const_map = $code_base->getGlobalConstantMap();
foreach ($const_map as $const) {
$stub_collection->addGlobalConstant($const);
}
$result .= $stub_collection->toString();
return $result;
}
}
StubsGenerator::main();
/**
* A representation of the collection of stubs for elements of a PHP module(a.k.a. extension).
*/
class StubCollection
{
/** @var CodeBase represents the known state of the code base we're extracting the stubs from. */
private $code_base;
public function __construct(CodeBase $code_base)
{
$this->code_base = $code_base;
}
/** @var string[][] a list of class stubs for a PHP module */
public $class_stubs = [];
/** @var string[][] a list of function stubs for a PHP module */
public $function_stubs = [];
/** @var string[][] a list of global constant stubs for a PHP module */
public $global_constant_stubs = [];
public function addClazz(Clazz $class) : void
{
[$namespace, $name] = $class->toStubInfo($this->code_base);
$this->class_stubs[$namespace][(string)$class->getFQSEN()] = $name;
}
public function addGlobalConstant(GlobalConstant $global_constant) : void
{
[$namespace, $name] = $global_constant->toStubInfo();
$this->global_constant_stubs[$namespace][(string)$global_constant->getFQSEN()] = $name;
}
public function addFunc(Func $function) : void
{
[$namespace, $name] = $function->toStubInfo();
$this->function_stubs[$namespace][(string)$function->getFQSEN()] = $name;
}
/** @return string[][] */
public function toCombinedStubs() : array
{
$result = [];
foreach ($this->class_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
foreach ($this->function_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
foreach ($this->global_constant_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
return $result;
}
/**
* Returns the accumulated stubs converted to inline PHP code.
*/
public function toString() : string
{
$parts = [];
foreach ($this->toCombinedStubs() as $namespace => $stubs) {
$concatenated_stubs_representation = implode('', $stubs);
$namespace_repr = ($namespace === '' ? '' : "$namespace ");
$parts[] = sprintf("namespace %s{\n%s}\n", $namespace_repr, $concatenated_stubs_representation);
}
return implode("\n", $parts);
}
}