1313namespace PhpCsFixer \Tokenizer \Analyzer ;
1414
1515use PhpCsFixer \Tokenizer \Analyzer \Analysis \ArgumentAnalysis ;
16+ use PhpCsFixer \Tokenizer \Analyzer \Analysis \NamespaceUseAnalysis ;
1617use PhpCsFixer \Tokenizer \Analyzer \Analysis \TypeAnalysis ;
1718use PhpCsFixer \Tokenizer \CT ;
19+ use PhpCsFixer \Tokenizer \Token ;
1820use PhpCsFixer \Tokenizer \Tokens ;
1921
2022/**
2325final class FunctionsAnalyzer
2426{
2527 /**
28+ * @var array
29+ */
30+ private $ functionsAnalysis = ['tokens ' => '' , 'imports ' => [], 'declarations ' => []];
31+
32+ /**
33+ * Important: risky because of the limited (file) scope of the tool.
34+ *
2635 * @param int $index
2736 *
2837 * @return bool
@@ -33,15 +42,85 @@ public function isGlobalFunctionCall(Tokens $tokens, $index)
3342 return false ;
3443 }
3544
45+ $ nextIndex = $ tokens ->getNextMeaningfulToken ($ index );
46+
47+ if (!$ tokens [$ nextIndex ]->equals ('( ' )) {
48+ return false ;
49+ }
50+
51+ $ previousIsNamespaceSeparator = false ;
3652 $ prevIndex = $ tokens ->getPrevMeaningfulToken ($ index );
53+
3754 if ($ tokens [$ prevIndex ]->isGivenKind (T_NS_SEPARATOR )) {
55+ $ previousIsNamespaceSeparator = true ;
3856 $ prevIndex = $ tokens ->getPrevMeaningfulToken ($ prevIndex );
3957 }
4058
41- $ nextIndex = $ tokens ->getNextMeaningfulToken ($ index );
59+ if ($ tokens [$ prevIndex ]->isGivenKind ([T_DOUBLE_COLON , T_FUNCTION , CT ::T_NAMESPACE_OPERATOR , T_NEW , T_OBJECT_OPERATOR , CT ::T_RETURN_REF , T_STRING ])) {
60+ return false ;
61+ }
62+
63+ if ($ previousIsNamespaceSeparator ) {
64+ return true ;
65+ }
66+
67+ if ($ tokens ->isChanged () || $ tokens ->getCodeHash () !== $ this ->functionsAnalysis ['tokens ' ]) {
68+ $ this ->buildFunctionsAnalysis ($ tokens );
69+ }
70+
71+ // figure out in which namespace we are
72+ $ namespaceAnalyzer = new NamespacesAnalyzer ();
73+
74+ $ declarations = $ namespaceAnalyzer ->getDeclarations ($ tokens );
75+ $ scopeStartIndex = 0 ;
76+ $ scopeEndIndex = \count ($ tokens ) - 1 ;
77+ $ inGlobalNamespace = false ;
78+
79+ foreach ($ declarations as $ declaration ) {
80+ $ scopeStartIndex = $ declaration ->getScopeStartIndex ();
81+ $ scopeEndIndex = $ declaration ->getScopeEndIndex ();
82+
83+ if ($ index >= $ scopeStartIndex && $ index <= $ scopeEndIndex ) {
84+ $ inGlobalNamespace = '' === $ declaration ->getFullName ();
85+
86+ break ;
87+ }
88+ }
89+
90+ $ call = strtolower ($ tokens [$ index ]->getContent ());
91+
92+ // check if the call is to a function declared in the same namespace as the call is done,
93+ // if the call is already in the global namespace than declared functions are in the same
94+ // global namespace and don't need checking
95+
96+ if (!$ inGlobalNamespace ) {
97+ /** @var int $functionNameIndex */
98+ foreach ($ this ->functionsAnalysis ['declarations ' ] as $ functionNameIndex ) {
99+ if ($ functionNameIndex < $ scopeStartIndex || $ functionNameIndex > $ scopeEndIndex ) {
100+ continue ;
101+ }
102+
103+ if (strtolower ($ tokens [$ functionNameIndex ]->getContent ()) === $ call ) {
104+ return false ;
105+ }
106+ }
107+ }
42108
43- return !$ tokens [$ prevIndex ]->isGivenKind ([T_DOUBLE_COLON , T_FUNCTION , CT ::T_NAMESPACE_OPERATOR , T_NEW , T_OBJECT_OPERATOR , CT ::T_RETURN_REF , T_STRING ])
44- && $ tokens [$ nextIndex ]->equals ('( ' );
109+ /** @var NamespaceUseAnalysis $functionUse */
110+ foreach ($ this ->functionsAnalysis ['imports ' ] as $ functionUse ) {
111+ if ($ functionUse ->getStartIndex () < $ scopeStartIndex || $ functionUse ->getEndIndex () > $ scopeEndIndex ) {
112+ continue ;
113+ }
114+
115+ if ($ call !== strtolower ($ functionUse ->getShortName ())) {
116+ continue ;
117+ }
118+
119+ // global import like `use function \str_repeat;`
120+ return $ functionUse ->getShortName () === ltrim ($ functionUse ->getFullName (), '\\' );
121+ }
122+
123+ return true ;
45124 }
46125
47126 /**
@@ -119,4 +198,65 @@ public function isTheSameClassCall(Tokens $tokens, $index)
119198 || $ tokens [$ operatorIndex ]->equals ([T_DOUBLE_COLON , ':: ' ]) && $ tokens [$ referenceIndex ]->equals ([T_STRING , 'self ' ], false )
120199 || $ tokens [$ operatorIndex ]->equals ([T_DOUBLE_COLON , ':: ' ]) && $ tokens [$ referenceIndex ]->equals ([T_STATIC , 'static ' ], false );
121200 }
201+
202+ private function buildFunctionsAnalysis (Tokens $ tokens )
203+ {
204+ $ this ->functionsAnalysis = [
205+ 'tokens ' => $ tokens ->getCodeHash (),
206+ 'imports ' => [],
207+ 'declarations ' => [],
208+ ];
209+
210+ // find declarations
211+
212+ if ($ tokens ->isTokenKindFound (T_FUNCTION )) {
213+ $ end = \count ($ tokens );
214+
215+ for ($ i = 0 ; $ i < $ end ; ++$ i ) {
216+ // skip classy, we are looking for functions not methods
217+ if ($ tokens [$ i ]->isGivenKind (Token::getClassyTokenKinds ())) {
218+ $ i = $ tokens ->getNextTokenOfKind ($ i , ['( ' , '{ ' ]);
219+
220+ if ($ tokens [$ i ]->equals ('( ' )) { // anonymous class
221+ $ i = $ tokens ->findBlockEnd (Tokens::BLOCK_TYPE_PARENTHESIS_BRACE , $ i );
222+ $ i = $ tokens ->getNextTokenOfKind ($ i , ['{ ' ]);
223+ }
224+
225+ $ i = $ tokens ->findBlockEnd (Tokens::BLOCK_TYPE_CURLY_BRACE , $ i );
226+
227+ continue ;
228+ }
229+
230+ if (!$ tokens [$ i ]->isGivenKind (T_FUNCTION )) {
231+ continue ;
232+ }
233+
234+ $ i = $ tokens ->getNextMeaningfulToken ($ i );
235+
236+ if ($ tokens [$ i ]->isGivenKind (CT ::T_RETURN_REF )) {
237+ $ i = $ tokens ->getNextMeaningfulToken ($ i );
238+ }
239+
240+ if (!$ tokens [$ i ]->isGivenKind (T_STRING )) {
241+ continue ;
242+ }
243+
244+ $ this ->functionsAnalysis ['declarations ' ][] = $ i ;
245+ }
246+ }
247+
248+ // find imported functions
249+
250+ $ namespaceUsesAnalyzer = new NamespaceUsesAnalyzer ();
251+
252+ if ($ tokens ->isTokenKindFound (CT ::T_FUNCTION_IMPORT )) {
253+ $ declarations = $ namespaceUsesAnalyzer ->getDeclarationsFromTokens ($ tokens );
254+
255+ foreach ($ declarations as $ declaration ) {
256+ if ($ declaration ->isFunction ()) {
257+ $ this ->functionsAnalysis ['imports ' ][] = $ declaration ;
258+ }
259+ }
260+ }
261+ }
122262}
0 commit comments