PocketMine-MP 5.18.2 git-00e39821f06a4b6d728d35053c2621dbb19369ff
generate-create-static-methods.php
1<?php
2
3/*
4 * This file is part of BedrockProtocol.
5 * Copyright (C) 2014-2022 PocketMine Team <https://github.com/pmmp/BedrockProtocol>
6 *
7 * BedrockProtocol is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 */
12
13declare(strict_types=1);
14
15namespace pocketmine\network\mcpe\protocol\tools\generate_create_static_methods;
16
17use function array_map;
18use function array_slice;
19use function basename;
20use function class_exists;
21use function count;
22use function dirname;
23use function file_get_contents;
24use function file_put_contents;
25use function implode;
26use function max;
27use function preg_match;
28use function preg_split;
29use function str_pad;
30use function str_repeat;
31use function strlen;
32use function substr;
33use function trim;
34
35require dirname(__DIR__) . '/vendor/autoload.php';
36
37function generateCreateFunction(\ReflectionClass $reflect, int $indentLevel, int $modifiers, string $methodName) : array{
38 $properties = [];
39 $paramTags = [];
40 $longestParamType = 0;
41 $phpstanParamTags = [];
42 $longestPhpstanParamType = 0;
43 foreach($reflect->getProperties() as $property){
44 if($property->getDeclaringClass()->getName() !== $reflect->getName()){
45 continue;
46 }
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]));
51 }
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]));
56 }
57 }
58
59 $lines = [];
60 $lines[] = "/**";
61 $lines[] = " * @generate-create-func";
62 foreach($paramTags as $paramTag){
63 $lines[] = " * @param " . str_pad($paramTag[0], $longestParamType, " ", STR_PAD_RIGHT) . " $" . $paramTag[1];
64 }
65 foreach($phpstanParamTags as $paramTag){
66 $lines[] = " * @phpstan-param " . str_pad($paramTag[0], $longestPhpstanParamType, " ", STR_PAD_RIGHT) . " $" . $paramTag[1];
67 }
68 $lines[] = " */";
69
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")
75 };
76 $funcStart = "$visibilityStr static function $methodName(";
77 $funcEnd = ") : self{";
78 $params = [];
79 foreach($properties as $name => $reflectProperty){
80 $stringType = "";
81 $propertyType = $reflectProperty->getType();
82
83 //this will generate FQNs, we leave them alone and let php-cs-fixer deal with them
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()));
88 }
89
90 $params[] = ($stringType !== "" ? "$stringType " : "") . "\$$name";
91 }
92 if(count($params) <= 6){
93 $lines[] = $funcStart . implode(", ", $params) . $funcEnd;
94 }else{
95 $lines[] = $funcStart;
96 foreach($params as $param){
97 $lines[] = "\t$param,";
98 }
99 $lines[] = $funcEnd;
100 }
101 if(count($params) > 0){
102 $lines[] = "\t\$result = new self;";
103 foreach($properties as $name => $reflectProperty){
104 $lines[] = "\t\$result->$name = \$$name;";
105 }
106 $lines[] = "\treturn \$result;";
107 }else{
108 $lines[] = "\treturn new self;";
109 }
110 $lines[] = "}";
111
112 return array_map(fn(string $line) => str_repeat("\t", $indentLevel) . $line, $lines);
113}
114
115foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(dirname(__DIR__) . '/src', \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
116 if(substr($file, -4) !== ".php"){
117 continue;
118 }
119 $contents = file_get_contents($file);
120 if($contents === false){
121 throw new \RuntimeException("Failed to get contents of $file");
122 }
123
124 if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
125 continue;
126 }
127 $shortClassName = basename($file, ".php");
128 $className = $matches[1] . "\\" . $shortClassName;
129 if(!class_exists($className)){
130 continue;
131 }
132 $reflect = new \ReflectionClass($className);
133 $newContents = $contents;
134 $modified = [];
135 foreach($reflect->getMethods(\ReflectionMethod::IS_STATIC) as $method){
136 if($method->getDeclaringClass()->getName() !== $reflect->getName() || $method->isAbstract()){
137 continue;
138 }
139
140 $docComment = $method->getDocComment();
141 if($docComment === false || preg_match('/@generate-create-func\s/', $docComment) !== 1){
142 continue;
143 }
144
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);
150
151 $modified[] = $method->getName();
152 }
153
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";
160 }else{
161 echo "No functions found with @generate-create-func tag in class $shortName\n";
162 }
163}