Skip to content

Instantly share code, notes, and snippets.

@dg
Last active May 31, 2018 15:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dg/14a9f37f1c6ac9bd77f96d050e8d5eed to your computer and use it in GitHub Desktop.
Save dg/14a9f37f1c6ac9bd77f96d050e8d5eed to your computer and use it in GitHub Desktop.
Converts @param & @return to native PHP typehints
<?php
declare(strict_types=1);
require '/nette.phar';
class TypeHints
{
public $php71 = true;
public $ignoredTypes = [
false => ['mixed', 'resource', 'object', 'iterable', 'void'], // PHP 7.0
true => ['mixed', 'resource', 'object', 'static'], // PHP 7.1
];
public $ignoredMethods = [
'offsetSet', 'offsetGet', 'offsetExists', 'offsetUnset',
//'__set', '__get', '__isset', '__unset',
];
public function update($file)
{
$s = file_get_contents($file);
$s = str_replace("\r\n", "\n", $s);
$s = preg_replace_callback('#((?>\n[ \t]*\/\*\*.+\*\/))?(\s+[\w ]*function &?)(\w+)\((.*)\)(:?+)#sU',
function ($m) {
list(, $doc, $inter, $name, $args, $hint) = $m;
$args = $args ? explode(', ', $args) : [];
$returnHint = '';
$this->processReturn($doc, $returnHint);
if (!in_array($name, $this->ignoredMethods, true)) {
$this->processParams($doc, $args);
}
if (trim($doc, "/* \t\n") === '') {
$doc = '';
}
return $doc . $inter . $name . '(' . implode(', ',
$args) . ')' . ($hint ?: ($returnHint ? ": $returnHint" : ''));
}, $s);
$s = str_replace("\n", "\r\n", $s);
file_put_contents($file, $s);
}
private function processReturn(string &$doc, string &$returnHint): void
{
$returnHint = '';
$doc = preg_replace_callback(
'#\n[ \t]*\* *@return +(\S+)( +.*)?()#',
function (array $m) use (&$returnHint) {
list($line, $type, $info) = $m;
$nnType = $this->php71 ? str_replace(['|null', '|NULL'], '', $type) : $type;
$nullable = $type !== $nnType ? '?' : '';
if (in_array($nnType, $this->ignoredTypes[$this->php71], true)) {
} elseif ($nnType === 'static') {
$returnHint = $nullable . 'self';
} elseif (preg_match('#^[\w\\\\]+\[\]\z#', $nnType)) {
$returnHint = $nullable . 'array';
} elseif (preg_match('#^[\w\\\\]+\z#', $nnType)) {
$returnHint = $nullable . $nnType;
if (!$info) {
return '';
}
}
return $line;
},
$doc
);
}
private function processParams(string &$doc, array &$args): void
{
$counter = 0;
$removed = false;
$doc = preg_replace_callback(
'#(\n[ \t]*\* *@param)(?: +(\S+)(?:\s+(\$\w+))?( +.*)?)?()#',
function (array $m) use (&$args, &$counter, &$removed, $doc) {
list(, $begin, $type, $name, $info) = $m;
if ($name) {
foreach ($args as $counter => &$arg) {
if (strpos($arg, $name) !== false) {
$counter++;
goto process;
}
}
return $m[0];
}
if (!isset($args[$counter])) {
echo "Too much @param in $doc\n";
return $m[0];
}
$arg = &$args[$counter++];
process:
if ($info && strpos($arg, trim($info)) !== false) {
$info = '';
}
$defaultType = $this->getDefaultType($arg);
$nnType = str_replace(['|null', '|NULL'], '', $type);
$nullable = ($type !== $nnType) && $defaultType !== 'null';
$hasHint = preg_match('#\S &?\$#', $arg);
if (preg_match('#^[\w\\\\]+\z#', $nnType) && !in_array($nnType, $this->ignoredTypes[$this->php71], true) && (!$nullable || $this->php71)) {
if (!$hasHint) {
$arg = ($nullable ? '?' : '') . $nnType . ' ' . $arg;
}
if (!$info) {
$removed = true;
return '';
}
} elseif (!$hasHint && $defaultType && $defaultType !== 'null') {
$arg = ($nullable ? '?' : '') . $defaultType . ' ' . $arg;
}
if ($removed && !$name) {
preg_match('#\$\w+#', $arg, $m);
$name = $m[0];
}
return rtrim($begin . ' ' . trim($type . ' ' . $name) . $info);
},
$doc
);
for (; $counter < count($args); $counter++) {
$arg = &$args[$counter];
$defaultType = $this->getDefaultType($arg);
$hasHint = preg_match('#\S &?\$#', $arg);
if (!$hasHint && $defaultType && $defaultType !== 'null') {
$arg = $defaultType . ' ' . $arg;
}
}
}
private function getDefaultType($arg)
{
if (preg_match('#= (?:(true|false)|(\d)|(["\'])|(\[)|(null))#i', $arg, $m)) {
if ($m[1]) {
return 'bool';
} elseif ($m[2] !== '') {
return 'int';
} elseif ($m[3]) {
return 'string';
} elseif ($m[4]) {
return 'array';
} elseif ($m[5]) {
return 'null';
}
}
}
}
$updater = new TypeHints;
foreach (Nette\Utils\Finder::findFiles('*.php')->from(__DIR__) as $file) {
echo $file, "\n";
$updater->update((string) $file);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment