typeComparator = $typeComparator; $this->phpVersionProvider = $phpVersionProvider; $this->staticTypeMapper = $staticTypeMapper; } public function getRuleDefinition() : RuleDefinition { return new RuleDefinition('Add param types where needed', [new ConfiguredCodeSample(<<<'CODE_SAMPLE' class SomeClass { public function process($name) { } } CODE_SAMPLE , <<<'CODE_SAMPLE' class SomeClass { public function process(string $name) { } } CODE_SAMPLE , [new AddParamTypeDeclaration('SomeClass', 'process', 0, new StringType())])]); } /** * @return array> */ public function getNodeTypes() : array { return [Class_::class, Interface_::class]; } /** * @param Class_|Interface_ $node */ public function refactor(Node $node) : ?Node { $this->hasChanged = \false; foreach ($node->getMethods() as $classMethod) { if ($this->shouldSkip($node, $classMethod)) { continue; } $methodName = $this->getName($classMethod); foreach ($this->addParamTypeDeclarations as $addParamTypeDeclaration) { if (!$this->nodeNameResolver->isStringName($methodName, $addParamTypeDeclaration->getMethodName())) { continue; } if (!$this->isObjectType($node, $addParamTypeDeclaration->getObjectType())) { continue; } $this->refactorClassMethodWithTypehintByParameterPosition($classMethod, $addParamTypeDeclaration); } } if (!$this->hasChanged) { return null; } return $node; } /** * @param mixed[] $configuration */ public function configure(array $configuration) : void { Assert::allIsAOf($configuration, AddParamTypeDeclaration::class); $this->addParamTypeDeclarations = $configuration; } /** * @param \PhpParser\Node\Stmt\Class_|\PhpParser\Node\Stmt\Interface_ $classLike */ private function shouldSkip($classLike, ClassMethod $classMethod) : bool { // skip class methods without args if ($classMethod->params === []) { return \true; } // skip class without parents/interfaces if ($classLike instanceof Class_ && $classLike->implements !== []) { return \false; } return !$classLike->extends instanceof Name; } private function refactorClassMethodWithTypehintByParameterPosition(ClassMethod $classMethod, AddParamTypeDeclaration $addParamTypeDeclaration) : void { $parameter = $classMethod->params[$addParamTypeDeclaration->getPosition()] ?? null; if (!$parameter instanceof Param) { return; } $this->refactorParameter($parameter, $addParamTypeDeclaration); } private function refactorParameter(Param $param, AddParamTypeDeclaration $addParamTypeDeclaration) : void { // already set → no change if ($param->type !== null) { $currentParamType = $this->staticTypeMapper->mapPhpParserNodePHPStanType($param->type); if ($this->typeComparator->areTypesEqual($currentParamType, $addParamTypeDeclaration->getParamType())) { return; } } $paramTypeNode = $this->staticTypeMapper->mapPHPStanTypeToPhpParserNode($addParamTypeDeclaration->getParamType(), TypeKind::PARAM); $this->hasChanged = \true; // remove it if ($addParamTypeDeclaration->getParamType() instanceof MixedType) { if ($this->phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::MIXED_TYPE)) { $param->type = $paramTypeNode; return; } $param->type = null; return; } $param->type = $paramTypeNode; } }