[WI-46976] Add stub map with its generation script

This commit is contained in:
Théo FIDRY 2019-06-06 23:32:10 +02:00
parent 4349d27af3
commit e873583e23
No known key found for this signature in database
GPG Key ID: F8C66BC24296DE7E
3 changed files with 11218 additions and 0 deletions

10934
PhpStormStubsMap.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,9 @@
"phpdocumentor/reflection-docblock": "^4.3",
"phpunit/phpunit": "7.1.4"
},
"autoload": {
"files": ["PhpStormStubsMap.php"]
},
"autoload-dev": {
"psr-4": {
"StubTests\\": "tests/"

281
generate-stub-map.php Normal file
View File

@ -0,0 +1,281 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
namespace JetBrains\PHPStormStub;
use DirectoryIterator;
use Exception;
use const PHP_EOL;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\NodeVisitorAbstract;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use function array_map;
use function count;
use function file_get_contents;
use function file_put_contents;
use function in_array;
use function ksort;
use function preg_match;
use RuntimeException;
use function sprintf;
use function str_replace;
use function strlen;
use function strtolower;
use function substr;
use function var_export;
(function () : void {
require __DIR__ . '/vendor/autoload.php';
class InvalidConstantNode extends RuntimeException
{
public static function create(Node $node) : self
{
return new self(sprintf(
'Invalid constant node (first 50 characters: %s)',
substr((new Standard())->prettyPrint([$node]), 0, 50)
));
}
}
class InvalidFileLocation extends RuntimeException
{
}
/**
* @throws InvalidFileLocation
*/
function assertReadableFile(string $filename) : void
{
if (empty($filename)) {
throw new InvalidFileLocation('Filename was empty');
}
if (! file_exists($filename)) {
throw new InvalidFileLocation(sprintf('File "%s" does not exist', $filename));
}
if (! is_readable($filename)) {
throw new InvalidFileLocation(sprintf('File "%s" is not readable', $filename));
}
if (! is_file($filename)) {
throw new InvalidFileLocation(sprintf('"%s" is not a file', $filename));
}
}
/**
* @throws InvalidConstantNode
*/
function assertValidDefineFunctionCall(Node\Expr\FuncCall $node) : void
{
if (! ($node->name instanceof Node\Name)) {
throw InvalidConstantNode::create($node);
}
if ($node->name->toLowerString() !== 'define') {
throw InvalidConstantNode::create($node);
}
if (! in_array(count($node->args), [2, 3], true)) {
throw InvalidConstantNode::create($node);
}
if (! ($node->args[0]->value instanceof Node\Scalar\String_)) {
throw InvalidConstantNode::create($node);
}
$valueNode = $node->args[1]->value;
if ($valueNode instanceof Node\Expr\FuncCall) {
throw InvalidConstantNode::create($node);
}
if ($valueNode instanceof Node\Expr\Variable) {
throw InvalidConstantNode::create($node);
}
}
$phpStormStubsDirectory = __DIR__;
$fileVisitor = new class() extends NodeVisitorAbstract
{
/** @var string[] */
private $classNames = [];
/** @var string[] */
private $functionNames = [];
/** @var string[] */
private $constantNames = [];
/**
* {@inheritdoc}
*/
public function enterNode(Node $node) : ?int
{
if ($node instanceof Node\Stmt\ClassLike) {
$this->classNames[] = $node->namespacedName->toString();
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
if ($node instanceof Node\Stmt\Function_) {
$this->functionNames[] = $node->namespacedName->toString();
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
if ($node instanceof Node\Const_) {
$this->constantNames[] = $node->namespacedName->toString();
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
if ($node instanceof Node\Expr\FuncCall) {
try {
assertValidDefineFunctionCall($node);
} catch (InvalidConstantNode $e) {
return null;
}
/** @var Node\Scalar\String_ $nameNode */
$nameNode = $node->args[0]->value;
if (count($node->args) === 3
&& $node->args[2]->value instanceof Node\Expr\ConstFetch
&& $node->args[2]->value->name->toLowerString() === 'true'
) {
$this->constantNames[] = strtolower($nameNode->value);
}
$this->constantNames[] = $nameNode->value;
return NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
return null;
}
/**
* @return string[]
*/
public function getClassNames() : array
{
return $this->classNames;
}
/**
* @return string[]
*/
public function getFunctionNames() : array
{
return $this->functionNames;
}
/**
* @return string[]
*/
public function getConstantNames() : array
{
return $this->constantNames;
}
public function clear() : void
{
$this->classNames = [];
$this->functionNames = [];
$this->constantNames = [];
}
};
$phpParser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$nodeTraverser = new NodeTraverser();
$nodeTraverser->addVisitor(new NameResolver());
$nodeTraverser->addVisitor($fileVisitor);
$map = ['classes' => [], 'functions' => [], 'constants' => []];
foreach (new DirectoryIterator($phpStormStubsDirectory) as $directoryInfo) {
/** @var DirectoryIterator $directoryInfo */
if ($directoryInfo->isDot()) {
continue;
}
if (! $directoryInfo->isDir()) {
continue;
}
if (in_array($directoryInfo->getBasename(), ['tests', 'meta'], true)) {
continue;
}
foreach (new DirectoryIterator($directoryInfo->getPathName()) as $fileInfo) {
/** @var DirectoryIterator $fileInfo */
if ($fileInfo->isDot()) {
continue;
}
if (! preg_match('/\.php$/', $fileInfo->getBasename())) {
continue;
}
assertReadableFile($fileInfo->getPathname());
echo sprintf('Parsing "%s"', $fileInfo->getPathname()).PHP_EOL;
$ast = $phpParser->parse(file_get_contents($fileInfo->getPathname()));
$nodeTraverser->traverse($ast);
foreach ($fileVisitor->getClassNames() as $className) {
$map['classes'][$className] = $fileInfo->getPathname();
}
foreach ($fileVisitor->getFunctionNames() as $functionName) {
$map['functions'][$functionName] = $fileInfo->getPathname();
}
foreach ($fileVisitor->getConstantNames() as $constantName) {
$map['constants'][$constantName] = $fileInfo->getPathname();
}
$fileVisitor->clear();
}
}
$mapWithRelativeFilePaths = array_map(static function (array $files) use ($phpStormStubsDirectory) : array {
ksort($files);
return array_map(static function (string $filePath) use ($phpStormStubsDirectory) : string {
return str_replace('\\', '/', substr($filePath, strlen($phpStormStubsDirectory) + 1));
}, $files);
}, $map);
$exportedClasses = var_export($mapWithRelativeFilePaths['classes'], true);
$exportedFunctions = var_export($mapWithRelativeFilePaths['functions'], true);
$exportedConstants = var_export($mapWithRelativeFilePaths['constants'], true);
$output = <<<"PHP"
<?php
declare(strict_types=1);
namespace JetBrains\PHPStormStub;
final class PhpStormStubsMap
{
const CLASSES = {$exportedClasses};
const FUNCTIONS = {$exportedFunctions};
const CONSTANTS = {$exportedConstants};
}
PHP;
$mapFile = __DIR__ . '/PhpStormStubsMap.php';
$bytesWritten = @file_put_contents($mapFile, $output);
if ($bytesWritten === false) {
throw new Exception(sprintf('File "%s" is not writeable.', $mapFile));
}
exit('Done');
})();