33namespace SlevomatCodingStandard \Sniffs \Namespaces ;
44
55use SlevomatCodingStandard \Helpers \ClassHelper ;
6+ use SlevomatCodingStandard \Helpers \ConstantHelper ;
7+ use SlevomatCodingStandard \Helpers \FunctionHelper ;
68use SlevomatCodingStandard \Helpers \NamespaceHelper ;
79use SlevomatCodingStandard \Helpers \ReferencedName ;
810use SlevomatCodingStandard \Helpers \ReferencedNameHelper ;
@@ -19,6 +21,8 @@ class ReferenceUsedNamesOnlySniff implements \PHP_CodeSniffer\Sniffs\Sniff
1921
2022 public const CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE = 'ReferenceViaFullyQualifiedNameWithoutNamespace ' ;
2123
24+ public const CODE_REFERENCE_VIA_FALLBACK_GLOBAL_NAME = 'ReferenceViaFallbackGlobalName ' ;
25+
2226 public const CODE_PARTIAL_USE = 'PartialUse ' ;
2327
2428 /** @var string[] */
@@ -36,9 +40,15 @@ class ReferenceUsedNamesOnlySniff implements \PHP_CodeSniffer\Sniffs\Sniff
3640 /** @var bool */
3741 public $ allowFullyQualifiedGlobalFunctions = false ;
3842
43+ /** @var bool */
44+ public $ allowFallbackGlobalFunctions = true ;
45+
3946 /** @var bool */
4047 public $ allowFullyQualifiedGlobalConstants = false ;
4148
49+ /** @var bool */
50+ public $ allowFallbackGlobalConstants = true ;
51+
4252 /** @var string[] */
4353 public $ specialExceptionNames = [];
4454
@@ -67,6 +77,12 @@ class ReferenceUsedNamesOnlySniff implements \PHP_CodeSniffer\Sniffs\Sniff
6777 /** @var bool */
6878 public $ allowFullyQualifiedNameForCollidingClasses = false ;
6979
80+ /** @var bool */
81+ public $ allowFullyQualifiedNameForCollidingFunctions = false ;
82+
83+ /** @var bool */
84+ public $ allowFullyQualifiedNameForCollidingConstants = false ;
85+
7086 /**
7187 * @return mixed[]
7288 */
@@ -140,17 +156,51 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $openTagPointer)
140156 $ tokens = $ phpcsFile ->getTokens ();
141157
142158 $ referencedNames = ReferencedNameHelper::getAllReferencedNames ($ phpcsFile , $ openTagPointer );
159+ $ useStatements = UseStatementHelper::getUseStatements ($ phpcsFile , $ openTagPointer );
160+
143161 $ definedClassesIndex = array_flip (array_map (function (string $ className ): string {
144162 return strtolower ($ className );
145163 }, ClassHelper::getAllNames ($ phpcsFile )));
164+ $ definedFunctionsIndex = array_flip (array_map (function (string $ functionName ): string {
165+ return strtolower ($ functionName );
166+ }, FunctionHelper::getAllFunctionNames ($ phpcsFile )));
167+ $ definedConstantsIndex = array_flip (ConstantHelper::getAllNames ($ phpcsFile ));
146168
147169 if ($ this ->allowFullyQualifiedNameForCollidingClasses ) {
148- $ referencesIndex = array_flip (
170+ $ classReferencesIndex = array_flip (
171+ array_map (
172+ function (ReferencedName $ referencedName ): string {
173+ return strtolower ($ referencedName ->getNameAsReferencedInFile ());
174+ },
175+ array_filter ($ referencedNames , function (ReferencedName $ referencedName ): bool {
176+ return $ referencedName ->isClass ();
177+ })
178+ )
179+ );
180+ }
181+
182+ if ($ this ->allowFullyQualifiedNameForCollidingFunctions ) {
183+ $ functionReferencesIndex = array_flip (
149184 array_map (
150185 function (ReferencedName $ referencedName ): string {
151186 return strtolower ($ referencedName ->getNameAsReferencedInFile ());
152187 },
153- $ referencedNames
188+ array_filter ($ referencedNames , function (ReferencedName $ referencedName ): bool {
189+ return $ referencedName ->isFunction ();
190+ })
191+ )
192+ );
193+ }
194+
195+ if ($ this ->allowFullyQualifiedNameForCollidingConstants ) {
196+ $ constantReferencesIndex = array_flip (
197+ array_map (
198+ function (ReferencedName $ referencedName ): string {
199+ return $ referencedName ->getNameAsReferencedInFile ();
200+ },
201+ array_filter ($ referencedNames , function (ReferencedName $ referencedName ): bool {
202+ return $ referencedName ->isConstant ();
203+ })
154204 )
155205 );
156206 }
@@ -159,16 +209,33 @@ function (ReferencedName $referencedName): string {
159209 $ name = $ referencedName ->getNameAsReferencedInFile ();
160210 $ nameStartPointer = $ referencedName ->getStartPointer ();
161211 $ canonicalName = NamespaceHelper::normalizeToCanonicalName ($ name );
162-
163- if ($ this ->allowFullyQualifiedNameForCollidingClasses ) {
164- $ unqualifiedClassName = strtolower (NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName ($ name ));
165- if (isset ($ referencesIndex [$ unqualifiedClassName ]) || array_key_exists ($ unqualifiedClassName , $ definedClassesIndex ?? [])) {
212+ $ unqualifiedName = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName ($ name );
213+
214+ $ isFullyQualified = NamespaceHelper::isFullyQualifiedName ($ name );
215+ $ isGlobalFallback = !$ isFullyQualified
216+ && !NamespaceHelper::hasNamespace ($ name )
217+ && !array_key_exists (UseStatement::getUniqueId ($ referencedName ->getType (), $ name ), $ useStatements );
218+ $ isGlobalFunctionFallback = $ referencedName ->isFunction () && $ isGlobalFallback ;
219+ $ isGlobalConstantFallback = $ referencedName ->isConstant () && $ isGlobalFallback ;
220+
221+ if ($ referencedName ->isClass () && $ this ->allowFullyQualifiedNameForCollidingClasses ) {
222+ $ lowerCasedUnqualifiedClassName = strtolower ($ unqualifiedName );
223+ if (isset ($ classReferencesIndex [$ lowerCasedUnqualifiedClassName ]) || array_key_exists ($ lowerCasedUnqualifiedClassName , $ definedClassesIndex )) {
224+ continue ;
225+ }
226+ } elseif ($ referencedName ->isFunction () && $ this ->allowFullyQualifiedNameForCollidingFunctions ) {
227+ $ lowerCasedUnqualifiedFunctionName = strtolower ($ unqualifiedName );
228+ if (isset ($ functionReferencesIndex [$ lowerCasedUnqualifiedFunctionName ]) || array_key_exists ($ lowerCasedUnqualifiedFunctionName , $ definedFunctionsIndex )) {
229+ continue ;
230+ }
231+ } elseif ($ referencedName ->isConstant () && $ this ->allowFullyQualifiedNameForCollidingConstants ) {
232+ if (isset ($ constantReferencesIndex [$ unqualifiedName ]) || array_key_exists ($ unqualifiedName , $ definedConstantsIndex )) {
166233 continue ;
167234 }
168235 }
169236
170- if (NamespaceHelper:: isFullyQualifiedName ( $ name ) ) {
171- if (!$ this ->isClassRequiredToBeUsed ($ name )) {
237+ if ($ isFullyQualified || $ isGlobalFunctionFallback || $ isGlobalConstantFallback ) {
238+ if ($ isFullyQualified && !$ this ->isRequiredToBeUsed ($ name )) {
172239 continue ;
173240 }
174241
@@ -185,12 +252,15 @@ function (ReferencedName $referencedName): string {
185252 $ previousKeywordPointer = TokenHelper::findPreviousExcluding ($ phpcsFile , array_merge (TokenHelper::$ nameTokenCodes , [T_WHITESPACE , T_COMMA ]), $ nameStartPointer - 1 );
186253 if (!in_array ($ tokens [$ previousKeywordPointer ]['code ' ], $ this ->getFullyQualifiedKeywords (), true )) {
187254 if (
188- !NamespaceHelper::hasNamespace ($ name )
255+ $ isFullyQualified
256+ && !NamespaceHelper::hasNamespace ($ name )
189257 && NamespaceHelper::findCurrentNamespaceName ($ phpcsFile , $ nameStartPointer ) === null
190258 ) {
259+ $ label = sprintf ($ referencedName ->isConstant () ? 'Constant %s ' : ($ referencedName ->isFunction () ? 'Function %s() ' : 'Class %s ' ), $ name );
260+
191261 $ fix = $ phpcsFile ->addFixableError (sprintf (
192- 'Type %s should not be referenced via a fully qualified name, but via an unqualified name without the leading \\, because the file does not have a namespace and the type cannot be put in a use statement. ' ,
193- $ name
262+ '%s should not be referenced via a fully qualified name, but via an unqualified name without the leading \\, because the file does not have a namespace and the type cannot be put in a use statement. ' ,
263+ $ label
194264 ), $ nameStartPointer , self ::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE );
195265 if ($ fix ) {
196266 $ phpcsFile ->fixer ->beginChangeset ();
@@ -201,9 +271,9 @@ function (ReferencedName $referencedName): string {
201271 $ shouldBeUsed = NamespaceHelper::hasNamespace ($ name );
202272 if (!$ shouldBeUsed ) {
203273 if ($ referencedName ->isFunction ()) {
204- $ shouldBeUsed = !$ this ->allowFullyQualifiedGlobalFunctions ;
274+ $ shouldBeUsed = $ isFullyQualified ? !$ this ->allowFullyQualifiedGlobalFunctions : ! $ this -> allowFallbackGlobalFunctions ;
205275 } elseif ($ referencedName ->isConstant ()) {
206- $ shouldBeUsed = !$ this ->allowFullyQualifiedGlobalConstants ;
276+ $ shouldBeUsed = $ isFullyQualified ? !$ this ->allowFullyQualifiedGlobalConstants : ! $ this -> allowFallbackGlobalConstants ;
207277 } else {
208278 $ shouldBeUsed = !$ this ->allowFullyQualifiedGlobalClasses ;
209279 }
@@ -213,27 +283,41 @@ function (ReferencedName $referencedName): string {
213283 continue ;
214284 }
215285
216- $ useStatements = UseStatementHelper::getUseStatements ($ phpcsFile , $ openTagPointer );
217286 $ nameToReference = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName ($ name );
218- $ canonicalNameToReference = strtolower ($ nameToReference );
287+ $ canonicalNameToReference = $ referencedName -> isConstant () ? $ nameToReference : strtolower ($ nameToReference );
219288
220289 $ canBeFixed = true ;
221290 foreach ($ useStatements as $ useStatement ) {
291+ if ($ useStatement ->getType () !== $ referencedName ->getType ()) {
292+ continue ;
293+ }
294+
295+ if ($ useStatement ->getFullyQualifiedTypeName () === $ canonicalName ) {
296+ continue ;
297+ }
298+
222299 if (
223- $ useStatement ->getType () === $ referencedName ->getType ()
224- && $ useStatement ->getFullyQualifiedTypeName () !== $ canonicalName
225- && ($ useStatement ->getCanonicalNameAsReferencedInFile () === $ canonicalNameToReference || array_key_exists ($ canonicalNameToReference , $ definedClassesIndex ))
300+ $ useStatement ->getCanonicalNameAsReferencedInFile () === $ canonicalNameToReference
301+ || ($ referencedName ->isClass () && array_key_exists ($ canonicalNameToReference , $ definedClassesIndex ))
302+ || ($ referencedName ->isFunction () && array_key_exists ($ canonicalNameToReference , $ definedFunctionsIndex ))
303+ || ($ referencedName ->isConstant () && array_key_exists ($ canonicalNameToReference , $ definedConstantsIndex ))
226304 ) {
227305 $ canBeFixed = false ;
228306 break ;
229307 }
230308 }
231309
232- $ errorMessage = sprintf ('Type %s should not be referenced via a fully qualified name, but via a use statement. ' , $ name );
310+ $ label = sprintf ($ referencedName ->isConstant () ? 'Constant %s ' : ($ referencedName ->isFunction () ? 'Function %s() ' : 'Class %s ' ), $ name );
311+ $ errorCode = $ isGlobalConstantFallback || $ isGlobalFunctionFallback
312+ ? self ::CODE_REFERENCE_VIA_FALLBACK_GLOBAL_NAME
313+ : self ::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME ;
314+ $ errorMessage = $ isGlobalConstantFallback || $ isGlobalFunctionFallback
315+ ? sprintf ('%s should not be referenced via a fallback global name, but via a use statement. ' , $ label )
316+ : sprintf ('%s should not be referenced via a fully qualified name, but via a use statement. ' , $ label );
233317 if ($ canBeFixed ) {
234- $ fix = $ phpcsFile ->addFixableError ($ errorMessage , $ nameStartPointer , self :: CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME );
318+ $ fix = $ phpcsFile ->addFixableError ($ errorMessage , $ nameStartPointer , $ errorCode );
235319 } else {
236- $ phpcsFile ->addError ($ errorMessage , $ nameStartPointer , self :: CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME );
320+ $ phpcsFile ->addError ($ errorMessage , $ nameStartPointer , $ errorCode );
237321 $ fix = false ;
238322 }
239323
@@ -286,7 +370,7 @@ function (ReferencedName $referencedName): string {
286370 }
287371 }
288372
289- private function isClassRequiredToBeUsed (string $ name ): bool
373+ private function isRequiredToBeUsed (string $ name ): bool
290374 {
291375 if (count ($ this ->namespacesRequiredToUse ) === 0 ) {
292376 return true ;
0 commit comments