propertyFetchFinder = $propertyFetchFinder;
$this->propertyWriteonlyAnalyzer = $propertyWriteonlyAnalyzer;
$this->phpDocInfoFactory = $phpDocInfoFactory;
}
public function getRuleDefinition() : RuleDefinition
{
return new RuleDefinition('Remove unused private properties', [new CodeSample(<<<'CODE_SAMPLE'
class SomeClass
{
private $property;
}
CODE_SAMPLE
, <<<'CODE_SAMPLE'
class SomeClass
{
}
CODE_SAMPLE
)]);
}
/**
* @return array>
*/
public function getNodeTypes() : array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactorWithScope(Node $node, Scope $scope) : ?Node
{
if ($this->shouldSkipClass($node)) {
return null;
}
$hasChanged = \false;
foreach ($node->stmts as $key => $stmt) {
if (!$stmt instanceof Property) {
continue;
}
if ($this->shouldSkipProperty($stmt)) {
continue;
}
if (!$this->shouldRemoveProperty($node, $stmt)) {
continue;
}
// remove property
unset($node->stmts[$key]);
$propertyName = $this->getName($stmt);
$this->removePropertyAssigns($node, $propertyName);
$hasChanged = \true;
}
if ($hasChanged) {
return $node;
}
return null;
}
private function shouldSkipProperty(Property $property) : bool
{
// has some attribute logic
if ($property->attrGroups !== []) {
return \true;
}
if (\count($property->props) !== 1) {
return \true;
}
if (!$property->isPrivate()) {
return \true;
}
// has some possible magic
if ($property->isStatic()) {
return \true;
}
$propertyPhpDocInfo = $this->phpDocInfoFactory->createFromNode($property);
if (!$propertyPhpDocInfo instanceof PhpDocInfo) {
return \false;
}
// skip as might contain important metadata
return $propertyPhpDocInfo->hasByType(DoctrineAnnotationTagValueNode::class);
}
private function shouldRemoveProperty(Class_ $class, Property $property) : bool
{
$propertyName = $this->getName($property);
$propertyFetches = $this->propertyFetchFinder->findLocalPropertyFetchesByName($class, $propertyName);
if ($propertyFetches === []) {
return \true;
}
return $this->propertyWriteonlyAnalyzer->arePropertyFetchesExclusivelyBeingAssignedTo($propertyFetches);
}
private function shouldSkipClass(Class_ $class) : bool
{
foreach ($class->stmts as $stmt) {
// unclear what property can be used there
if ($stmt instanceof TraitUse) {
return \true;
}
}
return $this->propertyWriteonlyAnalyzer->hasClassDynamicPropertyNames($class);
}
private function removePropertyAssigns(Class_ $class, string $propertyName) : void
{
$this->traverseNodesWithCallable($class, function (Node $node) use($class, $propertyName) {
if (!$node instanceof Expression && !$node instanceof Return_) {
return null;
}
if (!$node->expr instanceof Assign) {
return null;
}
$assign = $node->expr;
if (!$this->propertyFetchFinder->isLocalPropertyFetchByName($assign->var, $class, $propertyName)) {
return null;
}
if ($node instanceof Expression) {
return NodeTraverser::REMOVE_NODE;
}
$node->expr = $node->expr->expr;
return $node;
});
}
}