22
33namespace Symplify \PHPStanRules \Rules \Complexity ;
44
5+ use PhpParser \Comment \Doc ;
56use PhpParser \Node ;
67use PhpParser \Node \Expr \Assign ;
78use PhpParser \Node \Expr \PropertyFetch ;
89use PhpParser \Node \Expr \Variable ;
10+ use PhpParser \Node \Stmt \Expression ;
911use PHPStan \Analyser \Scope ;
1012use PHPStan \Rules \Rule ;
1113use PHPStan \Rules \RuleError ;
1214use PHPStan \Rules \RuleErrorBuilder ;
1315use Symfony \Component \Form \AbstractType ;
1416use Symplify \PHPStanRules \Enum \RuleIdentifier ;
17+ use Symplify \PHPStanRules \PhpDoc \PhpDocResolver ;
1518
1619/**
1720 * @see \Symplify\PHPStanRules\Tests\Rules\Complexity\NoJustPropertyAssignRule\NoJustPropertyAssignRuleTest
1821 *
19- * @implements Rule<Assign >
22+ * @implements Rule<Expression >
2023 */
2124final class NoJustPropertyAssignRule implements Rule
2225{
@@ -25,30 +28,78 @@ final class NoJustPropertyAssignRule implements Rule
2528 */
2629 public const ERROR_MESSAGE = 'Instead of assigning service property to a variable, use the property directly ' ;
2730
31+ public function __construct (
32+ private readonly PhpDocResolver $ phpDocResolver
33+ ) {
34+
35+ }
36+
2837 public function getNodeType (): string
2938 {
30- return Assign ::class;
39+ return Expression ::class;
3140 }
3241
3342 /**
34- * @param Assign $node
43+ * @param Expression $node
3544 * @return RuleError[]
3645 */
3746 public function processNode (Node $ node , Scope $ scope ): array
3847 {
39- if (! $ this ->isLocalPropertyFetchAssignToVariable ($ node , $ scope )) {
48+ $ expr = $ node ->expr ;
49+
50+ if (! $ expr instanceof Assign) {
51+ return [];
52+ }
53+
54+ if (! $ this ->isLocalPropertyFetchAssignToVariable ($ expr , $ scope )) {
4055 return [];
4156 }
4257
4358 if ($ this ->shouldSkipCurrentClass ($ scope )) {
4459 return [];
4560 }
4661
62+ if ($ this ->shoulSkipMoreSpecificTypeByDocblock ($ scope , $ node , $ node ->expr )) {
63+ return [];
64+ }
65+
4766 return [RuleErrorBuilder::message (self ::ERROR_MESSAGE )
4867 ->identifier (RuleIdentifier::NO_JUST_PROPERTY_ASSIGN )
4968 ->build ()];
5069 }
5170
71+ private function shoulSkipMoreSpecificTypeByDocblock (Scope $ scope , Expression $ node , Assign $ assign ): bool
72+ {
73+ $ docComment = $ node ->getDocComment ();
74+ if (! $ docComment instanceof Doc) {
75+ return false ;
76+ }
77+
78+ /** @var Variable $variable */
79+ $ variable = $ assign ->var ;
80+ $ varName = $ variable ->name ;
81+
82+ if ($ varName === null ) {
83+ return false ;
84+ }
85+
86+ $ r = $ this ->phpDocResolver ->resolve ($ scope , $ scope ->getClassReflection (), $ docComment );
87+ $ exprType = $ scope ->getType ($ assign ->expr );
88+
89+ foreach ($ r ->getVarTags () as $ key => $ varTag ) {
90+ if ($ key !== $ varName ) {
91+ continue ;
92+ }
93+
94+ // different type means more specific type on purpose
95+ if (! $ varTag ->getType ()->equals ($ exprType )) {
96+ return true ;
97+ }
98+ }
99+
100+ return false ;
101+ }
102+
52103 private function isLocalPropertyFetchAssignToVariable (Assign $ assign , Scope $ scope ): bool
53104 {
54105 if (! $ assign ->var instanceof Variable) {
0 commit comments