13declare(strict_types=1);
15namespace pocketmine\network\mcpe\protocol\tools\generate_create_static_methods;
17use
function array_map;
18use
function array_slice;
20use
function class_exists;
23use
function file_get_contents;
24use
function file_put_contents;
27use
function preg_match;
28use
function preg_split;
30use
function str_repeat;
35require dirname(__DIR__) .
'/vendor/autoload.php';
37function generateCreateFunction(\ReflectionClass $reflect,
int $indentLevel,
int $modifiers,
string $methodName) : array{
40 $longestParamType = 0;
41 $phpstanParamTags = [];
42 $longestPhpstanParamType = 0;
43 foreach($reflect->getProperties() as $property){
44 if($property->getDeclaringClass()->getName() !== $reflect->getName()){
47 $properties[$property->getName()] = $property;
48 if(($phpDoc = $property->getDocComment()) !==
false && preg_match(
'/@var ([A-Za-z\d\[\]\\\\]+)/', $phpDoc, $matches) === 1){
49 $paramTags[] = [$matches[1], $property->getName()];
50 $longestParamType = max($longestParamType, strlen($matches[1]));
52 if(($phpDoc = $property->getDocComment()) !==
false && preg_match(
'/@phpstan-var ([A-Za-z\d\[\]\\\\<>:\*\(\)\$ ,]+)/', substr($phpDoc, 3, -2), $matches) === 1){
53 $matches[1] = trim($matches[1]);
54 $phpstanParamTags[] = [$matches[1], $property->getName()];
55 $longestPhpstanParamType = max($longestPhpstanParamType, strlen($matches[1]));
61 $lines[] =
" * @generate-create-func";
62 foreach($paramTags as $paramTag){
63 $lines[] =
" * @param " . str_pad($paramTag[0], $longestParamType,
" ", STR_PAD_RIGHT) .
" $" . $paramTag[1];
65 foreach($phpstanParamTags as $paramTag){
66 $lines[] =
" * @phpstan-param " . str_pad($paramTag[0], $longestPhpstanParamType,
" ", STR_PAD_RIGHT) .
" $" . $paramTag[1];
70 $visibilityStr = match(
true){
71 ($modifiers & \ReflectionMethod::IS_PUBLIC) !== 0 =>
"public",
72 ($modifiers & \ReflectionMethod::IS_PRIVATE) !== 0 =>
"private",
73 ($modifiers & \ReflectionMethod::IS_PROTECTED) !== 0 =>
"protected",
74 default =>
throw new \InvalidArgumentException(
"Visibility must be a ReflectionMethod visibility constant")
76 $funcStart =
"$visibilityStr static function $methodName(";
77 $funcEnd =
") : self{";
79 foreach($properties as $name => $reflectProperty){
81 $propertyType = $reflectProperty->getType();
84 if($propertyType instanceof \ReflectionNamedType){
85 $stringType = ($propertyType->allowsNull() ?
"?" :
"") . ($propertyType->isBuiltin() ?
"" :
"\\") . $propertyType->getName();
86 }elseif($propertyType instanceof \ReflectionUnionType){
87 $stringType = implode(
"|", array_map(fn(\ReflectionNamedType $subType) => ($subType->isBuiltin() ?
"" :
"\\") . $subType->getName(), $propertyType->getTypes()));
90 $params[] = ($stringType !==
"" ?
"$stringType " :
"") .
"\$$name";
92 if(count($params) <= 6){
93 $lines[] = $funcStart . implode(
", ", $params) . $funcEnd;
95 $lines[] = $funcStart;
96 foreach($params as $param){
97 $lines[] =
"\t$param,";
101 if(count($params) > 0){
102 $lines[] =
"\t\$result = new self;";
103 foreach($properties as $name => $reflectProperty){
104 $lines[] =
"\t\$result->$name = \$$name;";
106 $lines[] =
"\treturn \$result;";
108 $lines[] =
"\treturn new self;";
112 return array_map(fn(
string $line) => str_repeat(
"\t", $indentLevel) . $line, $lines);
115foreach(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(dirname(__DIR__) .
'/src', \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
116 if(substr($file, -4) !==
".php"){
119 $contents = file_get_contents($file);
120 if($contents ===
false){
121 throw new \RuntimeException(
"Failed to get contents of $file");
124 if(preg_match(
"/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match(
'/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
127 $shortClassName = basename($file,
".php");
128 $className = $matches[1] .
"\\" . $shortClassName;
129 if(!class_exists($className)){
132 $reflect = new \ReflectionClass($className);
133 $newContents = $contents;
135 foreach($reflect->getMethods(\ReflectionMethod::IS_STATIC) as $method){
136 if($method->getDeclaringClass()->getName() !== $reflect->getName() || $method->isAbstract()){
140 $docComment = $method->getDocComment();
141 if($docComment ===
false || preg_match(
'/@generate-create-func\s/', $docComment) !== 1){
145 $lines = preg_split(
"/(*ANYCRLF)\n/", $newContents);
146 $docCommentLines = preg_split(
"/(*ANYCRLF)\n/", $docComment);
147 $beforeLines = array_slice($lines, 0, $method->getStartLine() - 1 - count($docCommentLines));
148 $afterLines = array_slice($lines, $method->getEndLine());
149 $newContents = implode(
"\n", $beforeLines) .
"\n" . implode(
"\n", generateCreateFunction($reflect, 1, $method->getModifiers(), $method->getName())) .
"\n" . implode(
"\n", $afterLines);
151 $modified[] = $method->getName();
154 $shortName = substr($reflect->getName(), strlen(
"pocketmine\\network\\mcpe\\protocol\\"));
155 if($newContents !== $contents){
156 file_put_contents($file, $newContents);
157 echo
"Successfully patched class $shortName: " . implode(
", ", $modified) .
"\n";
158 }elseif(count($modified) > 0){
159 echo
"Already up to date class $shortName: " . implode(
", ", $modified) .
"\n";
161 echo
"No functions found with @generate-create-func tag in class $shortName\n";