Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add update script and update property map
See #7983
- Loading branch information
1 parent
c58bb55
commit 90c7c63
Showing
2 changed files
with
498 additions
and
370 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
#!/usr/bin/env php | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
// Original Idea for the code in here came from: | ||
// https://github.com/phan/phan/blob/93c1c2/src/Phan/Language/Internal/PropertyMap.php#L49 | ||
// We differ in some places however: | ||
// 1. we only scan the "reference" subdirectory, as in the other directories there appears to be nothing valuable to | ||
// be found. | ||
// 2. we parse the XML and extract original class and property names instead of the normalized identifiers. | ||
|
||
const OVERRIDES = [ | ||
'DateInterval' => [ | ||
// documented as mixed in docs. | ||
'days' => 'false|int', | ||
] | ||
]; | ||
|
||
set_error_handler(function ($num, $str, $file, $line, $context = null) { | ||
throw new ErrorException($str, 0, $num, $file, $line); | ||
}); | ||
|
||
$docDir = realpath(__DIR__ . '/../build/doc-en'); | ||
|
||
if (false === $docDir) { | ||
echo 'PHP doc not found!' . PHP_EOL; | ||
echo 'Please execute: git clone git@github.com:php/doc-en.git ' . dirname(__DIR__) . '/build/doc-en'; | ||
} | ||
|
||
$files = iterator_to_array( | ||
new RegexIterator( | ||
new RecursiveIteratorIterator( | ||
new RecursiveDirectoryIterator( | ||
$docDir . '/reference', | ||
FilesystemIterator::CURRENT_AS_PATHNAME|FilesystemIterator::SKIP_DOTS | ||
), | ||
RecursiveIteratorIterator::LEAVES_ONLY | ||
), | ||
'/.*.xml$/' | ||
) | ||
); | ||
|
||
$classes = []; | ||
foreach ($files as $file) { | ||
$contents = file_get_contents($file); | ||
// FIXME: find a way to ignore custom entities, for now we strip them. | ||
$contents = preg_replace('#&[a-zA-Z\d.\-_]+;#', '', $contents); | ||
$contents = preg_replace('#%[a-zA-Z\d.\-_]+;#', '', $contents); | ||
$contents = preg_replace('#<!ENTITY[^>]+>#', '', $contents); | ||
try { | ||
$simple = new \SimpleXMLElement($contents); | ||
} catch (\Throwable $exception) { | ||
// FIXME: we ignore files with XML errors at the moment because the input XML is not always sober. | ||
// Examples are rpminfo/entities.functions.xml, wkhtmltox/wkhtmltox/bits/web.xml, | ||
// wkhtmltox/wkhtmltox/bits/load.xml | ||
echo sprintf("%1\$s: Ignoring %2\$s: %3\$s\n", $file, get_class($exception), $exception->getMessage()); | ||
continue; | ||
} | ||
|
||
$namespaces = $simple->getNamespaces(); | ||
$simple->registerXPathNamespace('docbook', 'http://docbook.org/ns/docbook'); | ||
foreach ($simple->xpath('//docbook:classsynopsis') as $classSynopsis) { | ||
$classSynopsis->registerXPathNamespace('docbook', 'http://docbook.org/ns/docbook'); | ||
$class = (string) $classSynopsis->xpath('./docbook:ooclass/docbook:classname')[0]; | ||
foreach ($classSynopsis->xpath('//docbook:fieldsynopsis') as $item) { | ||
assert($item instanceof SimpleXMLElement); | ||
$property = (string) $item->varname; | ||
if (null !== ($override = OVERRIDES[$class][$property] ?? null)) { | ||
$classes[$class][$property] = $override; | ||
continue; | ||
} | ||
|
||
$type = $item->type[0]; | ||
if (null === $type) { | ||
continue; | ||
} | ||
assert($type instanceof SimpleXMLElement); | ||
$typeClass = $type->attributes(/*'http://docbook.org/ns/docbook'*/)->class; | ||
if (null === $typeClass) { | ||
$type = (string) $type; | ||
} else if ('union' === (string) $typeClass) { | ||
$types = []; | ||
foreach ($type as $subType) { | ||
$types[] = (string) $subType; | ||
} | ||
$type = implode('|', $types); | ||
} | ||
switch ($type) { | ||
case '': | ||
// Some properties are not properly defined - we ignore them then. | ||
continue 2; | ||
// case 'integer': | ||
// $type = 'int'; | ||
default: | ||
} | ||
$modifier = (string) $item->modifier; | ||
// We do not want to handle constants... I guess?! | ||
if ('const' === $modifier) { | ||
continue; | ||
} | ||
|
||
$classes[$class][$property] = $type; | ||
} | ||
} | ||
} | ||
|
||
function serializeArray(array $array, string $prefix): string | ||
{ | ||
uksort($array, function (string $first, string $second): int { | ||
return strtolower($first) <=> strtolower($second); | ||
}); | ||
$result = "[\n"; | ||
$localPrefix = $prefix . ' '; | ||
foreach ($array as $key => $value) { | ||
$result .= $localPrefix . var_export((string) $key, true) . ' => ' . | ||
(is_array($value) | ||
? serializeArray($value, $localPrefix) | ||
: var_export($value, true)) . ",\n"; | ||
} | ||
$result .= $prefix . ']'; | ||
|
||
return $result; | ||
} | ||
|
||
$serialized = serializeArray($classes, ''); | ||
file_put_contents( | ||
dirname(__DIR__) . '/dictionaries/PropertyMap.php', | ||
<<<EOF | ||
<?php | ||
namespace Psalm\Internal; | ||
/** | ||
* Automatically created by bin/update-property-map.php | ||
* | ||
* Please do not modify - adapt the override constants in above file instead. | ||
*/ | ||
return $serialized; | ||
EOF | ||
); |
Oops, something went wrong.