22
33namespace PHPStan \Command \Neon2Attributes ;
44
5+ use PhpParser \ErrorHandler \Collecting ;
56use PhpParser \Internal \TokenStream ;
67use PhpParser \Node ;
78use PhpParser \NodeTraverser ;
89use PhpParser \NodeVisitor \CloningVisitor ;
10+ use PhpParser \NodeVisitor \NameResolver ;
911use PhpParser \ParserFactory ;
1012use PHPStan \DependencyInjection \AutowiredService ;
1113use PHPStan \File \FileReader ;
14+ use PHPStan \Fixable \NewClassNamesVisitor ;
1215use PHPStan \Fixable \PhpPrinter ;
1316use PHPStan \Fixable \PhpPrinterIndentationDetectorVisitor ;
1417use PHPStan \ShouldNotHappenException ;
1518use function array_is_list ;
16- use function array_key_exists ;
1719use function count ;
1820use function is_array ;
1921use function is_bool ;
2426use function str_contains ;
2527use function str_repeat ;
2628use function strcasecmp ;
27- use function strrpos ;
28- use function strtolower ;
29- use function substr ;
3029
3130/**
3231 * Adds attributes to classes and their constructor parameters, keeping the rest of the file as it was.
@@ -62,28 +61,35 @@ public function write(string $file, array $edits): string
6261 $ this ->addAttributes ($ file , $ newStmts , $ edit );
6362 }
6463
64+ $ nameResolver = new NameResolver (new Collecting (), ['replaceNodes ' => false ]);
65+ /** @var list<Node\Stmt> $newStmts */
66+ $ newStmts = (new NodeTraverser (
67+ $ nameResolver ,
68+ new NewClassNamesVisitor ($ nameResolver ->getNameContext (), importNames: true , preferSelf: false ),
69+ ))->traverse ($ newStmts );
70+
6571 return (new AttributesPrinter (['indent ' => $ indent ]))->printFormatPreserving ($ newStmts , $ oldStmts , $ tokens );
6672 }
6773
6874 /**
6975 * @param list<Node\Stmt> $stmts
7076 */
71- private function addAttributes (string $ file , array & $ stmts , ClassAttributes $ edit ): void
77+ private function addAttributes (string $ file , array $ stmts , ClassAttributes $ edit ): void
7278 {
7379 $ className = $ edit ->reflection ->getName ();
7480 $ namespace = $ edit ->reflection ->getNamespaceName ();
7581 $ shortName = $ edit ->reflection ->getShortName ();
7682
7783 if ($ namespace === '' ) {
78- $ this ->addAttributesInScope ($ stmts , '' , $ shortName , $ edit );
84+ $ this ->addAttributesInScope ($ stmts , $ shortName , $ edit );
7985 return ;
8086 }
8187
8288 foreach ($ stmts as $ stmt ) {
8389 if (!$ stmt instanceof Node \Stmt \Namespace_ || $ stmt ->name === null || strcasecmp ($ stmt ->name ->toString (), $ namespace ) !== 0 ) {
8490 continue ;
8591 }
86- if ($ this ->addAttributesInScope ($ stmt ->stmts , $ namespace , $ shortName , $ edit )) {
92+ if ($ this ->addAttributesInScope ($ stmt ->stmts , $ shortName , $ edit )) {
8793 return ;
8894 }
8995 }
@@ -94,7 +100,7 @@ private function addAttributes(string $file, array &$stmts, ClassAttributes $edi
94100 /**
95101 * @param list<Node\Stmt> $stmts statements of a namespace, or of a file without one
96102 */
97- private function addAttributesInScope (array & $ stmts, string $ namespace , string $ shortName , ClassAttributes $ edit ): bool
103+ private function addAttributesInScope (array $ stmts , string $ shortName , ClassAttributes $ edit ): bool
98104 {
99105 $ class = null ;
100106 foreach ($ stmts as $ stmt ) {
@@ -109,8 +115,7 @@ private function addAttributesInScope(array &$stmts, string $namespace, string $
109115 return false ;
110116 }
111117
112- $ scope = new ImportScope ($ namespace , $ stmts );
113- $ class ->attrGroups [] = new Node \AttributeGroup ([$ this ->createAttribute ($ scope , $ edit ->attribute )]);
118+ $ class ->attrGroups [] = new Node \AttributeGroup ([$ this ->createAttribute ($ edit ->attribute )]);
114119
115120 $ constructor = $ class ->getMethod ('__construct ' );
116121 $ isOneLine = $ constructor !== null
@@ -125,41 +130,44 @@ private function addAttributesInScope(array &$stmts, string $namespace, string $
125130 continue ;
126131 }
127132
128- $ param ->attrGroups [] = new Node \AttributeGroup ([$ this ->createAttribute ($ scope , $ attribute )]);
133+ $ param ->attrGroups [] = new Node \AttributeGroup ([$ this ->createAttribute ($ attribute )]);
129134 $ param ->setAttribute (AttributesPrinter::INLINE_ATTRIBUTES , $ isOneLine );
130135 }
131136 }
132137
133- $ stmts = $ scope ->stmts ;
134-
135138 return true ;
136139 }
137140
138- private function createAttribute (ImportScope $ scope , AttributeSpec $ attribute ): Node \Attribute
141+ private function createAttribute (AttributeSpec $ attribute ): Node \Attribute
139142 {
140- return new Node \Attribute ($ this -> createName ( $ scope , self ::DI_NAMESPACE . $ attribute ->name ), $ this ->createArgs ($ scope , $ attribute ));
143+ return new Node \Attribute (new Node \ Name \ FullyQualified ( self ::DI_NAMESPACE . $ attribute ->name ), $ this ->createArgs ($ attribute ));
141144 }
142145
143146 /**
144147 * @return list<Node\Arg>
145148 */
146- private function createArgs (ImportScope $ scope , AttributeSpec $ attribute ): array
149+ private function createArgs (AttributeSpec $ attribute ): array
147150 {
148151 $ args = [];
149152 foreach ($ attribute ->arguments as $ name => $ value ) {
150- $ args [] = new Node \Arg ($ this ->createExpr ($ scope , $ value ), name: new Node \Identifier ($ name ));
153+ $ args [] = new Node \Arg ($ this ->createExpr ($ value ), name: new Node \Identifier ($ name ));
151154 }
152155
153156 return $ args ;
154157 }
155158
156- private function createExpr (ImportScope $ scope , mixed $ value ): Node \Expr
159+ private function createExpr (mixed $ value ): Node \Expr
157160 {
158161 if ($ value instanceof AttributeSpec) {
159- return new Node \Expr \New_ ($ this -> createName ( $ scope , self ::DI_NAMESPACE . $ value ->name ), $ this ->createArgs ($ scope , $ value ));
162+ return new Node \Expr \New_ (new Node \ Name \ FullyQualified ( self ::DI_NAMESPACE . $ value ->name ), $ this ->createArgs ($ value ));
160163 }
161164 if ($ value instanceof ClassReference) {
162- return new Node \Expr \ClassConstFetch ($ this ->createName ($ scope , $ value ->className ), 'class ' );
165+ $ className = ltrim ($ value ->className , '\\' );
166+ if ($ className === '' ) {
167+ throw new ShouldNotHappenException ('Empty class name. ' );
168+ }
169+
170+ return new Node \Expr \ClassConstFetch (new Node \Name \FullyQualified ($ className ), 'class ' );
163171 }
164172 if (is_string ($ value )) {
165173 return new Node \Scalar \String_ ($ value );
@@ -175,8 +183,8 @@ private function createExpr(ImportScope $scope, mixed $value): Node\Expr
175183 $ isList = array_is_list ($ value );
176184 foreach ($ value as $ key => $ item ) {
177185 $ items [] = new Node \ArrayItem (
178- $ this ->createExpr ($ scope , $ item ),
179- $ isList ? null : $ this ->createExpr ($ scope , $ key ),
186+ $ this ->createExpr ($ item ),
187+ $ isList ? null : $ this ->createExpr ($ key ),
180188 );
181189 }
182190
@@ -186,36 +194,4 @@ private function createExpr(ImportScope $scope, mixed $value): Node\Expr
186194 throw new ShouldNotHappenException ();
187195 }
188196
189- /**
190- * Uses the short name and adds a use statement unless the short name already means another class in the file.
191- */
192- private function createName (ImportScope $ scope , string $ className ): Node \Name
193- {
194- $ className = ltrim ($ className , '\\' );
195- $ position = strrpos ($ className , '\\' );
196- $ shortName = $ position === false ? $ className : substr ($ className , $ position + 1 );
197- if ($ className === '' || $ shortName === '' ) {
198- throw new ShouldNotHappenException (sprintf ('Invalid class name %s. ' , $ className ));
199- }
200- $ lowerShortName = strtolower ($ shortName );
201-
202- if (array_key_exists ($ lowerShortName , $ scope ->uses )) {
203- return strcasecmp ($ scope ->uses [$ lowerShortName ], $ className ) === 0
204- ? new Node \Name ($ shortName )
205- : new Node \Name \FullyQualified ($ className );
206- }
207-
208- $ localName = $ scope ->namespace === '' ? $ shortName : $ scope ->namespace . '\\' . $ shortName ;
209- if (strcasecmp ($ localName , $ className ) === 0 ) {
210- return new Node \Name ($ shortName );
211- }
212- if (array_key_exists ($ lowerShortName , $ scope ->unqualifiedNames )) {
213- return new Node \Name \FullyQualified ($ className );
214- }
215-
216- $ scope ->addUse ($ className , $ lowerShortName );
217-
218- return new Node \Name ($ shortName );
219- }
220-
221197}
0 commit comments