diff --git a/README.md b/README.md index b9e2e1888..6f2b715ab 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ PocketMine-MP accepts community contributions! The following resources will be u * [Building and running PocketMine-MP from source](BUILDING.md) * [Contributing Guidelines](CONTRIBUTING.md) +New here? Check out [issues with the "Easy task" label](https://github.com/pmmp/PocketMine-MP/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22Easy%20task%22) for things you could work to familiarise yourself with the codebase. + ## Donate PocketMine-MP is free, but it requires a lot of time and effort from unpaid volunteers to develop. Donations enable us to keep delivering support for new versions and adding features your players love. diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 552d6d29d..4ac7b4410 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -10,6 +10,7 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon rules: + - pocketmine\phpstan\rules\DisallowDynamicNewRule - pocketmine\phpstan\rules\DisallowForeachByReferenceRule - pocketmine\phpstan\rules\ExplodeLimitRule - pocketmine\phpstan\rules\UnsafeForeachArrayOfStringRule diff --git a/src/crafting/ShapedRecipe.php b/src/crafting/ShapedRecipe.php index 4c40eb0f5..2af3f5134 100644 --- a/src/crafting/ShapedRecipe.php +++ b/src/crafting/ShapedRecipe.php @@ -97,6 +97,7 @@ class ShapedRecipe implements CraftingRecipe{ $this->shape = $shape; + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); foreach(Utils::stringifyKeys($ingredients) as $char => $i){ if(!str_contains(implode($this->shape), $char)){ throw new \InvalidArgumentException("Symbol '$char' does not appear in the recipe shape"); @@ -105,6 +106,7 @@ class ShapedRecipe implements CraftingRecipe{ $this->ingredientList[$char] = clone $i; } + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } diff --git a/src/crafting/ShapelessRecipe.php b/src/crafting/ShapelessRecipe.php index 7a4a22fda..b139439ef 100644 --- a/src/crafting/ShapelessRecipe.php +++ b/src/crafting/ShapelessRecipe.php @@ -53,7 +53,9 @@ class ShapelessRecipe implements CraftingRecipe{ if(count($ingredients) > 9){ throw new \InvalidArgumentException("Shapeless recipes cannot have more than 9 ingredients"); } + Utils::validateArrayValueType($ingredients, function(RecipeIngredient $_) : void{}); $this->ingredients = $ingredients; + Utils::validateArrayValueType($results, function(Item $_) : void{}); $this->results = Utils::cloneObjectArray($results); } diff --git a/tests/phpstan/configs/actual-problems.neon b/tests/phpstan/configs/actual-problems.neon index 73b7a65c1..d3adde422 100644 --- a/tests/phpstan/configs/actual-problems.neon +++ b/tests/phpstan/configs/actual-problems.neon @@ -18,6 +18,12 @@ parameters: count: 1 path: ../../../src/Server.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/Server.php + - message: '#^Method pocketmine\\Server\:\:getCommandAliases\(\) should return array\\> but returns array\\>\.$#' identifier: return.type @@ -54,6 +60,12 @@ parameters: count: 1 path: ../../../src/VersionInfo.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/block/Block.php + - message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getBlockAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -510,6 +522,12 @@ parameters: count: 3 path: ../../../src/block/tile/Spawnable.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/block/tile/TileFactory.php + - message: '#^Parameter \#1 \$x of method pocketmine\\world\\World\:\:getPotentialLightAt\(\) expects int, float\|int given\.$#' identifier: argument.type @@ -936,6 +954,12 @@ parameters: count: 4 path: ../../../src/plugin/PluginManager.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/plugin/PluginManager.php + - message: '#^Method pocketmine\\resourcepacks\\ZippedResourcePack\:\:getPackSize\(\) should return int but returns int\<0, max\>\|false\.$#' identifier: return.type @@ -1248,6 +1272,12 @@ parameters: count: 1 path: ../../../src/world/format/io/region/RegionLoader.php + - + message: '#^Dynamic new is not allowed\.$#' + identifier: pocketmine.new.dynamic + count: 1 + path: ../../../src/world/generator/GeneratorRegisterTask.php + - message: '#^Method pocketmine\\world\\generator\\biome\\BiomeSelector\:\:pickBiome\(\) should return pocketmine\\world\\biome\\Biome but returns pocketmine\\world\\biome\\Biome\|null\.$#' identifier: return.type diff --git a/tests/phpstan/rules/DisallowDynamicNewRule.php b/tests/phpstan/rules/DisallowDynamicNewRule.php new file mode 100644 index 000000000..c25e6a18b --- /dev/null +++ b/tests/phpstan/rules/DisallowDynamicNewRule.php @@ -0,0 +1,55 @@ + + */ +final class DisallowDynamicNewRule implements Rule{ + + public function getNodeType() : string{ + return New_::class; + } + + public function processNode(Node $node, Scope $scope) : array{ + /** @var New_ $node */ + if($node->class instanceof Expr){ + return [ + RuleErrorBuilder::message("Dynamic new is not allowed.") + ->tip("For factories, use closures instead. Closures can implement custom logic, are statically analyzable, and don't restrict the constructor signature.") + ->identifier("pocketmine.new.dynamic") + ->build() + ]; + } + + return []; + } +}