44
55use PhpParser \Node ;
66use PhpParser \Node \Arg ;
7+ use PhpParser \Node \Expr ;
78use PhpParser \Node \Expr \FuncCall ;
89use PhpParser \Node \Expr \Include_ ;
910use PhpParser \Node \Name \FullyQualified ;
1011use PHPStan \Analyser \Scope ;
1112use PHPStan \DependencyInjection \AutowiredParameter ;
1213use PHPStan \DependencyInjection \RegisteredRule ;
1314use PHPStan \File \FileHelper ;
15+ use PHPStan \Node \Printer \ExprPrinter ;
1416use PHPStan \Rules \IdentifierRuleError ;
1517use PHPStan \Rules \Rule ;
1618use PHPStan \Rules \RuleErrorBuilder ;
1719use PHPStan \ShouldNotHappenException ;
20+ use PHPStan \Type \Constant \ConstantStringType ;
1821use function array_merge ;
1922use function dirname ;
2023use function explode ;
@@ -33,6 +36,7 @@ final class RequireFileExistsRule implements Rule
3336 public function __construct (
3437 #[AutowiredParameter]
3538 private string $ currentWorkingDirectory ,
39+ private ExprPrinter $ exprPrinter ,
3640 )
3741 {
3842 }
@@ -49,14 +53,23 @@ public function processNode(Node $node, Scope $scope): array
4953 }
5054
5155 $ errors = [];
52- $ paths = $ this ->resolveFilePaths ($ node , $ scope );
56+ $ usedMagicDirFallback = false ;
57+ $ paths = $ this ->resolveFilePaths ($ node ->expr , $ scope , $ usedMagicDirFallback );
5358
5459 foreach ($ paths as $ path ) {
60+ $ path = $ path ->getValue ();
61+
5562 if ($ this ->doesFileExist ($ path , $ scope )) {
5663 continue ;
5764 }
5865
59- $ errors [] = $ this ->getErrorMessage ($ node , $ path );
66+ if ($ usedMagicDirFallback ) {
67+ $ pathExpr = $ this ->exprPrinter ->printExpr ($ node ->expr );
68+ } else {
69+ $ pathExpr = '" ' . $ path . '" ' ;
70+ }
71+
72+ $ errors [] = $ this ->getErrorMessage ($ node , $ pathExpr );
6073 }
6174
6275 return $ errors ;
@@ -97,7 +110,7 @@ private function doesFileExistForDirectory(string $path, string $workingDirector
97110
98111 private function getErrorMessage (Include_ $ node , string $ filePath ): IdentifierRuleError
99112 {
100- $ message = 'Path in %s() "%s" is not a file or it does not exist. ' ;
113+ $ message = 'Path in %s() %s is not a file or it does not exist. ' ;
101114
102115 switch ($ node ->type ) {
103116 case Include_::TYPE_REQUIRE :
@@ -132,18 +145,33 @@ private function getErrorMessage(Include_ $node, string $filePath): IdentifierRu
132145 }
133146
134147 /**
135- * @return array<string >
148+ * @return list<ConstantStringType >
136149 */
137- private function resolveFilePaths (Include_ $ node , Scope $ scope ): array
150+ private function resolveFilePaths (Expr $ expr , Scope $ scope, bool & $ magicDirFallback ): array
138151 {
139- $ paths = [];
140- $ type = $ scope ->getType ($ node ->expr );
141- $ constantStrings = $ type ->getConstantStrings ();
152+ $ magicDirFallback = false ;
142153
143- foreach ( $ constantStrings as $ constantString ) {
144- $ paths [] = $ constantString -> getValue ();
154+ if (! $ expr instanceof Expr \ BinaryOp \Concat ) {
155+ return $ scope -> getType ( $ expr )-> getConstantStrings ();
145156 }
146157
158+ if ($ expr ->left instanceof Node \Scalar \MagicConst \Dir) {
159+ $ magicDirFallback = true ;
160+
161+ $ paths = [];
162+ foreach ($ scope ->getType ($ expr ->right )->getConstantStrings () as $ constantString ) {
163+ $ paths [] = new ConstantStringType (dirname ($ scope ->getFile ()) . $ constantString ->getValue ());
164+ }
165+ return $ paths ;
166+ }
167+
168+ $ paths = [];
169+ $ rightPaths = $ this ->resolveFilePaths ($ expr ->right , $ scope , $ magicDirFallback );
170+ foreach ($ this ->resolveFilePaths ($ expr ->left , $ scope , $ magicDirFallback ) as $ left ) {
171+ foreach ($ rightPaths as $ rightPath ) {
172+ $ paths [] = new ConstantStringType ($ left ->getValue () . $ rightPath ->getValue ());
173+ }
174+ }
147175 return $ paths ;
148176 }
149177
0 commit comments