From 63ad7fba43a76691117e3a07d800630a55a44c22 Mon Sep 17 00:00:00 2001 From: Vossik Date: Mon, 8 Nov 2021 20:10:03 +0100 Subject: [PATCH 1/3] added ReferenceUsedNamesAfterUsageSniff --- ...nstructorPropertyPromotionSpacingSniff.php | 20 +- .../RequireMultiLineNullCoalesceSniff.php | 3 +- .../SwitchCommentSpacingSniff.php | 9 +- .../ReferenceUsedNamesAfterUsageSniff.php | 476 ++++++++++++++++++ .../TypeHints/UnionTypeHintFormatSniff.php | 47 +- .../WhiteSpace/MemberVarSpacingSniff.php | 19 +- InfinityloopCodingStandard/ruleset.xml | 6 + README.md | 12 +- composer.json | 3 +- 9 files changed, 551 insertions(+), 44 deletions(-) create mode 100644 InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php diff --git a/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php b/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php index 990d52e..ad1a1ca 100644 --- a/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php +++ b/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php @@ -4,6 +4,10 @@ namespace InfinityloopCodingStandard\Sniffs\Classes; +use \SlevomatCodingStandard\Helpers\FunctionHelper; +use \SlevomatCodingStandard\Helpers\SniffSettingsHelper; +use \SlevomatCodingStandard\Helpers\TokenHelper; + class ConstructorPropertyPromotionSpacingSniff implements \PHP_CodeSniffer\Sniffs\Sniff { public const CONSTRUCTOR_PARAMETER_SAME_LINE = 'ConstructorParametersOnSameLine'; @@ -15,19 +19,19 @@ public function register() : array public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $functionPointer) : void { - if (!\SlevomatCodingStandard\Helpers\SniffSettingsHelper::isEnabledByPhpVersion(null, 80000)) { + if (!SniffSettingsHelper::isEnabledByPhpVersion(null, 80000)) { return; } $tokens = $phpcsFile->getTokens(); - $namePointer = \SlevomatCodingStandard\Helpers\TokenHelper::findNextEffective($phpcsFile, $functionPointer + 1); + $namePointer = TokenHelper::findNextEffective($phpcsFile, $functionPointer + 1); if (\strtolower($tokens[$namePointer]['content']) !== '__construct') { return; } - if (\SlevomatCodingStandard\Helpers\FunctionHelper::isAbstract($phpcsFile, $functionPointer)) { + if (FunctionHelper::isAbstract($phpcsFile, $functionPointer)) { return; } @@ -40,13 +44,13 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $functionPointer $containsPropertyPromotion = false; foreach ($parameterPointers as $parameterPointer) { - $pointerBefore = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious( + $pointerBefore = TokenHelper::findPrevious( $phpcsFile, [\T_COMMA, \T_OPEN_PARENTHESIS], $parameterPointer - 1, ); - $visibilityPointer = \SlevomatCodingStandard\Helpers\TokenHelper::findNextEffective($phpcsFile, $pointerBefore + 1); + $visibilityPointer = TokenHelper::findNextEffective($phpcsFile, $pointerBefore + 1); if (\in_array($tokens[$visibilityPointer]['code'], \PHP_CodeSniffer\Util\Tokens::$scopeModifiers, true)) { $containsPropertyPromotion = true; @@ -58,7 +62,7 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $functionPointer } if (\count($parameterPointers) === 1) { - $pointerBefore = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious( + $pointerBefore = TokenHelper::findPrevious( $phpcsFile, [\T_COMMA, \T_OPEN_PARENTHESIS], $parameterPointers[0], @@ -110,7 +114,7 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $functionPointer $phpcsFile->fixer->beginChangeset(); - $pointerBefore = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious( + $pointerBefore = TokenHelper::findPrevious( $phpcsFile, [\T_COMMA, \T_OPEN_PARENTHESIS], $parameterPointer - 1, @@ -126,7 +130,7 @@ private function getParameterPointers(\PHP_CodeSniffer\Files\File $phpcsFile, in { $tokens = $phpcsFile->getTokens(); - return \SlevomatCodingStandard\Helpers\TokenHelper::findNextAll( + return TokenHelper::findNextAll( $phpcsFile, \T_VARIABLE, $tokens[$functionPointer]['parenthesis_opener'] + 1, diff --git a/InfinityloopCodingStandard/Sniffs/ControlStructures/RequireMultiLineNullCoalesceSniff.php b/InfinityloopCodingStandard/Sniffs/ControlStructures/RequireMultiLineNullCoalesceSniff.php index 840f9b2..41c9ee5 100644 --- a/InfinityloopCodingStandard/Sniffs/ControlStructures/RequireMultiLineNullCoalesceSniff.php +++ b/InfinityloopCodingStandard/Sniffs/ControlStructures/RequireMultiLineNullCoalesceSniff.php @@ -4,6 +4,7 @@ namespace InfinityloopCodingStandard\Sniffs\ControlStructures; +use \PHP_CodeSniffer\Files\File; use \SlevomatCodingStandard\Helpers\TokenHelper; class RequireMultiLineNullCoalesceSniff implements \PHP_CodeSniffer\Sniffs\Sniff @@ -24,7 +25,7 @@ public function register() : array } //@phpcs:ignore Squiz.Commenting.FunctionComment.ScalarTypeHintMissing - public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $coalescePointer) : void + public function process(File $phpcsFile, $coalescePointer) : void { $tokens = $phpcsFile->getTokens(); diff --git a/InfinityloopCodingStandard/Sniffs/ControlStructures/SwitchCommentSpacingSniff.php b/InfinityloopCodingStandard/Sniffs/ControlStructures/SwitchCommentSpacingSniff.php index 8f79cc9..7a30b76 100644 --- a/InfinityloopCodingStandard/Sniffs/ControlStructures/SwitchCommentSpacingSniff.php +++ b/InfinityloopCodingStandard/Sniffs/ControlStructures/SwitchCommentSpacingSniff.php @@ -4,6 +4,7 @@ namespace InfinityloopCodingStandard\Sniffs\ControlStructures; +use \PHP_CodeSniffer\Files\File; use \SlevomatCodingStandard\Helpers\TokenHelper; class SwitchCommentSpacingSniff implements \PHP_CodeSniffer\Sniffs\Sniff @@ -21,7 +22,7 @@ public function register() : array } //@phpcs:ignore Squiz.Commenting.FunctionComment.ScalarTypeHintMissing - public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr) : void + public function process(File $phpcsFile, $stackPtr) : void { $tokens = $phpcsFile->getTokens(); @@ -127,7 +128,7 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr) : voi } } - private function findNextCase(\PHP_CodeSniffer\Files\File $phpcsFile, int|bool|null $stackPtr, ?int $end = null) : bool|int + private function findNextCase(File $phpcsFile, int|bool|null $stackPtr, ?int $end = null) : bool|int { $tokens = $phpcsFile->getTokens(); @@ -145,7 +146,7 @@ private function findNextCase(\PHP_CodeSniffer\Files\File $phpcsFile, int|bool|n return $stackPtr; } - private function getEndOfLineBefore(\PHP_CodeSniffer\Files\File $phpcsFile, int $pointer) : int + private function getEndOfLineBefore(File $phpcsFile, int $pointer) : int { $tokens = $phpcsFile->getTokens(); @@ -193,7 +194,7 @@ private function getEndOfLineBefore(\PHP_CodeSniffer\Files\File $phpcsFile, int return $endOfLineBefore; } - private function getIndentation(\PHP_CodeSniffer\Files\File $phpcsFile, int $endOfLinePointer) : string + private function getIndentation(File $phpcsFile, int $endOfLinePointer) : string { $pointerAfterWhitespace = TokenHelper::findNextExcluding($phpcsFile, \T_WHITESPACE, $endOfLinePointer + 1); $actualIndentation = TokenHelper::getContent($phpcsFile, $endOfLinePointer + 1, $pointerAfterWhitespace - 1); diff --git a/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php new file mode 100644 index 0000000..e8f51c1 --- /dev/null +++ b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php @@ -0,0 +1,476 @@ + + */ + public function register() : array + { + return [ + \T_OPEN_TAG, + ]; + } + + //@phpcs:ignore Squiz.Commenting.FunctionComment.ScalarTypeHintMissing + public function process(File $phpcsFile, $openTagPointer) : void + { + if (TokenHelper::findPrevious($phpcsFile, \T_OPEN_TAG, $openTagPointer - 1) !== null) { + return; + } + + $tokens = $phpcsFile->getTokens(); + + $references = $this->getReferences($phpcsFile, $openTagPointer); + + $definedClassesIndex = []; + + foreach (ClassHelper::getAllNames($phpcsFile) as $definedClassPointer => $definedClassName) { + $definedClassesIndex[\strtolower($definedClassName)] = NamespaceHelper::resolveClassName( + $phpcsFile, + $definedClassName, + $definedClassPointer, + ); + } + + $definedFunctionsIndex = \array_flip(\array_map(static function (string $functionName) : string { + return \strtolower($functionName); + }, FunctionHelper::getAllFunctionNames($phpcsFile))); + $definedConstantsIndex = \array_flip(ConstantHelper::getAllNames($phpcsFile)); + + $classReferencesIndex = []; + $classReferences = \array_filter($references, static function (\stdClass $reference) : bool { + return $reference->source === self::SOURCE_CODE && $reference->isClass; + }); + + foreach ($classReferences as $classReference) { + $classReferencesIndex[\strtolower($classReference->name)] = NamespaceHelper::resolveName( + $phpcsFile, + $classReference->name, + $classReference->type, + $classReference->startPointer, + ); + } + + $namespacePointers = NamespaceHelper::getAllNamespacesPointers($phpcsFile); + $referenceErrors = []; + $referenced = []; + + foreach ($references as $reference) { + $canonicalName = NamespaceHelper::normalizeToCanonicalName($reference->name); + + if (isset($referenced[$canonicalName])) { + $referenced[$canonicalName]++; + } else { + $referenced[$canonicalName] = 1; + } + } + + foreach ($references as $reference) { + $useStatements = UseStatementHelper::getUseStatementsForPointer($phpcsFile, $reference->startPointer); + $name = $reference->name; + $startPointer = $reference->startPointer; + $canonicalName = NamespaceHelper::normalizeToCanonicalName($name); + $isFullyQualified = NamespaceHelper::isFullyQualifiedName($name); + $isGlobalFallback = !$isFullyQualified + && !NamespaceHelper::hasNamespace($name) + && $namespacePointers !== [] + && !\array_key_exists(UseStatement::getUniqueId($reference->type, $name), $useStatements); + $isGlobalFunctionFallback = false; + + if ($reference->isFunction && $isGlobalFallback) { + $isGlobalFunctionFallback = !\array_key_exists(\strtolower($reference->name), $definedFunctionsIndex) && \function_exists( + $reference->name, + ); + } + + $isGlobalConstantFallback = false; + + if ($reference->isConstant && $isGlobalFallback) { + $isGlobalConstantFallback = !\array_key_exists($reference->name, $definedConstantsIndex) && \defined($reference->name); + } + + if ($isFullyQualified || $isGlobalFunctionFallback || $isGlobalConstantFallback) { + if ($isFullyQualified && !$this->isRequiredToBeUsed($name)) { + continue; + } + } + + //Skip functions and constants + if ($reference->isFunction || $reference->isConstant) { + continue; + } + + //Ignore stdClass + if ($reference->isClass === true && $canonicalName === 'stdClass') { + continue; + } + + if ( + $isFullyQualified + && !NamespaceHelper::hasNamespace($name) + && $namespacePointers === [] + ) { + $label = \sprintf('Class %s', $name); + + $fix = $phpcsFile->addFixableError(\sprintf( + '%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.', + $label, + ), $startPointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE); + + if ($fix) { + $phpcsFile->fixer->beginChangeset(); + + if ($reference->source === self::SOURCE_ANNOTATION) { + $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( + $phpcsFile, + $reference->annotation, + $reference->nameNode, + new IdentifierTypeNode(\substr($reference->name, 1)), + ); + + $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + + for ($i = $startPointer + 1; $i <= $reference->endPointer; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } elseif ($reference->source === self::SOURCE_ANNOTATION_CONSTANT_FETCH) { + $fixedAnnotationContent = AnnotationHelper::fixAnnotationConstantFetchNode( + $phpcsFile, + $reference->annotation, + $reference->constantFetchNode, + new ConstFetchNode(\substr($reference->name, 1), $reference->constantFetchNode->name), + ); + + $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + + for ($i = $startPointer + 1; $i <= $reference->endPointer; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } else { + $phpcsFile->fixer->replaceToken($startPointer, \substr($tokens[$startPointer]['content'], 1)); + } + + $phpcsFile->fixer->endChangeset(); + } + } else { + $shouldBeUsed = NamespaceHelper::hasNamespace($name); + + if (!$shouldBeUsed) { + $shouldBeUsed = $isFullyQualified; + } + + if (!$shouldBeUsed + || ($this->count !== null && $referenced[$canonicalName] < $this->count) + && ($this->length !== null && $this->length > \strlen($canonicalName)) + ) { + continue; + } + + $reason = ''; + + if ($referenced[$canonicalName] >= $this->count) { + $reason = 'because it\'s used more than ' . $this->count . ' times.'; + } + + if ($this->length !== null && $this->length < \strlen($canonicalName)) { + $reason = $reason === '' + ? 'because it\'s length is more than ' . $this->length . ' symbols.' + : 'because it\'s used more than ' . $this->count . ' times and it\'s length is more than ' + . $this->length . ' symbols.'; + } + + $referenceErrors[] = (object) [ + 'reference' => $reference, + 'canonicalName' => $canonicalName, + 'isGlobalConstantFallback' => $isGlobalConstantFallback, + 'isGlobalFunctionFallback' => $isGlobalFunctionFallback, + 'reason' => $reason, + ]; + } + } + + if (\count($referenceErrors) === 0) { + return; + } + + $alreadyAddedUses = [ + UseStatement::TYPE_CLASS => [], + UseStatement::TYPE_FUNCTION => [], + UseStatement::TYPE_CONSTANT => [], + ]; + + $phpcsFile->fixer->beginChangeset(); + + foreach ($referenceErrors as $referenceData) { + $reference = $referenceData->reference; + $startPointer = $reference->startPointer; + $canonicalName = $referenceData->canonicalName; + $nameToReference = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($reference->name); + $canonicalNameToReference = $reference->isConstant + ? $nameToReference + : \strtolower($nameToReference); + $isGlobalConstantFallback = $referenceData->isGlobalConstantFallback; + $isGlobalFunctionFallback = $referenceData->isGlobalFunctionFallback; + + $useStatements = UseStatementHelper::getUseStatementsForPointer($phpcsFile, $reference->startPointer); + + $canBeFixed = \array_reduce( + $alreadyAddedUses[$reference->type], + static function (bool $carry, string $use) use ($canonicalName) : bool { + $useLastName = \strtolower(NamespaceHelper::getLastNamePart($use)); + $canonicalLastName = \strtolower(NamespaceHelper::getLastNamePart($canonicalName)); + + return $useLastName === $canonicalLastName + ? false + : $carry; + }, + true, + ); + + if ( + ( + $reference->isClass + && \array_key_exists($canonicalNameToReference, $definedClassesIndex) + && $canonicalName !== NamespaceHelper::normalizeToCanonicalName($definedClassesIndex[$canonicalNameToReference]) + ) + || ( + $reference->isClass + && \array_key_exists($canonicalNameToReference, $classReferencesIndex) + && $canonicalName !== NamespaceHelper::normalizeToCanonicalName($classReferencesIndex[$canonicalNameToReference]) + ) + || ($reference->isFunction && \array_key_exists($canonicalNameToReference, $definedFunctionsIndex)) + || ($reference->isConstant && \array_key_exists($canonicalNameToReference, $definedConstantsIndex)) + ) { + $canBeFixed = false; + } + + foreach ($useStatements as $useStatement) { + if ($useStatement->getType() !== $reference->type) { + continue; + } + + if ($useStatement->getFullyQualifiedTypeName() === $canonicalName) { + continue; + } + + if ($useStatement->getCanonicalNameAsReferencedInFile() !== $canonicalNameToReference) { + continue; + } + + $canBeFixed = false; + + break; + } + + $label = \sprintf('Class %s', $reference->name); + $errorCode = $isGlobalConstantFallback || $isGlobalFunctionFallback + ? self::CODE_REFERENCE_VIA_FALLBACK_GLOBAL_NAME + : self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME; + $errorMessage = $isGlobalConstantFallback || $isGlobalFunctionFallback + ? \sprintf('%s should not be referenced via a fallback global name, but via a use statement ' . $referenceData->reason, $label) + : \sprintf('%s should not be referenced via a fully qualified name, but via a use statement ' . $referenceData->reason, $label); + + if (!$canBeFixed) { + $phpcsFile->addError($errorMessage, $startPointer, $errorCode); + + continue; + } + + $fix = $phpcsFile->addFixableError($errorMessage, $startPointer, $errorCode); + + if (!$fix) { + continue; + } + + $addUse = !\in_array($canonicalName, $alreadyAddedUses[$reference->type], true); + + if ($reference->isClass + && \array_key_exists($canonicalNameToReference, $definedClassesIndex) + ) { + $addUse = false; + } + + foreach ($useStatements as $useStatement) { + if ($useStatement->getType() !== $reference->type + || $useStatement->getFullyQualifiedTypeName() !== $canonicalName + ) { + continue; + } + + $nameToReference = $useStatement->getNameAsReferencedInFile(); + $addUse = false; + } + + if ($addUse) { + $useStatementPlacePointer = $this->getUseStatementPlacePointer($phpcsFile, $openTagPointer, $useStatements); + $useTypeName = UseStatement::getTypeName($reference->type); + $useTypeFormatted = $useTypeName !== null + ? \sprintf('%s ', $useTypeName) + : ''; + + $phpcsFile->fixer->addNewline($useStatementPlacePointer); + $phpcsFile->fixer->addContent($useStatementPlacePointer, \sprintf('use %s%s;', $useTypeFormatted, $canonicalName)); + + $alreadyAddedUses[$reference->type][] = $canonicalName; + } + + if ($reference->source === self::SOURCE_ANNOTATION) { + $fixedAnnotationContent = AnnotationHelper::fixAnnotationType( + $phpcsFile, + $reference->annotation, + $reference->nameNode, + new IdentifierTypeNode($nameToReference), + ); + $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + } elseif ($reference->source === self::SOURCE_ANNOTATION_CONSTANT_FETCH) { + $fixedAnnotationContent = AnnotationHelper::fixAnnotationConstantFetchNode( + $phpcsFile, + $reference->annotation, + $reference->constantFetchNode, + new ConstFetchNode($nameToReference, $reference->constantFetchNode->name), + ); + $phpcsFile->fixer->replaceToken($startPointer, $fixedAnnotationContent); + } elseif ($reference->source === self::SOURCE_ATTRIBUTE) { + $attributeContent = TokenHelper::getContent($phpcsFile, $startPointer, $reference->endPointer); + $fixedAttributeContent = \preg_replace( + '~(?<=\W)' . \preg_quote($reference->name, '~') . '(?=\W)~', + $nameToReference, + $attributeContent, + ); + $phpcsFile->fixer->replaceToken($startPointer, $fixedAttributeContent); + } else { + $phpcsFile->fixer->replaceToken($startPointer, $nameToReference); + } + + for ($i = $startPointer + 1; $i <= $reference->endPointer; $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + } + + $phpcsFile->fixer->endChangeset(); + } + + private function isRequiredToBeUsed(string $name) : bool + { + return true; + } + + private function getUseStatementPlacePointer(\PHP_CodeSniffer\Files\File $phpcsFile, int $openTagPointer, array $useStatements) : int + { + if (\count($useStatements) !== 0) { + $lastUseStatement = \array_values($useStatements)[\count($useStatements) - 1]; + + return TokenHelper::findNext($phpcsFile, \T_SEMICOLON, $lastUseStatement->getPointer() + 1); + } + + $namespacePointer = TokenHelper::findNext($phpcsFile, \T_NAMESPACE, $openTagPointer + 1); + + if ($namespacePointer !== null) { + return TokenHelper::findNext($phpcsFile, [\T_SEMICOLON, \T_OPEN_CURLY_BRACKET], $namespacePointer + 1); + } + + $tokens = $phpcsFile->getTokens(); + + $useStatementPlacePointer = $openTagPointer; + + $nonWhitespacePointerAfterOpenTag = TokenHelper::findNextExcluding($phpcsFile, \T_WHITESPACE, $openTagPointer + 1); + + if (\in_array($tokens[$nonWhitespacePointerAfterOpenTag]['code'], \PHP_CodeSniffer\Util\Tokens::$commentTokens, true)) { + $commentEndPointer = CommentHelper::getCommentEndPointer($phpcsFile, $nonWhitespacePointerAfterOpenTag); + + if (StringHelper::endsWith($tokens[$commentEndPointer]['content'], $phpcsFile->eolChar)) { + $useStatementPlacePointer = $commentEndPointer; + } else { + $newLineAfterComment = $commentEndPointer + 1; + + if (\array_key_exists($newLineAfterComment, $tokens) && $tokens[$newLineAfterComment]['content'] === $phpcsFile->eolChar) { + $pointerAfterCommentEnd = TokenHelper::findNextExcluding($phpcsFile, \T_WHITESPACE, $newLineAfterComment + 1); + + //@phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong + if (TokenHelper::findNextContent($phpcsFile, \T_WHITESPACE, $phpcsFile->eolChar, $newLineAfterComment + 1, $pointerAfterCommentEnd) !== null) { + $useStatementPlacePointer = $commentEndPointer; + } + } + } + } + + $pointerAfter = TokenHelper::findNextEffective($phpcsFile, $useStatementPlacePointer + 1); + + if ($tokens[$pointerAfter]['code'] === \T_DECLARE) { + return TokenHelper::findNext($phpcsFile, \T_SEMICOLON, $pointerAfter + 1); + } + + return $useStatementPlacePointer; + } + + private function getReferences(\PHP_CodeSniffer\Files\File $phpcsFile, int $openTagPointer) : array + { + $references = []; + + foreach (ReferencedNameHelper::getAllReferencedNames($phpcsFile, $openTagPointer) as $referencedName) { + $reference = new \stdClass(); + $reference->source = self::SOURCE_CODE; + $reference->name = $referencedName->getNameAsReferencedInFile(); + $reference->type = $referencedName->getType(); + $reference->startPointer = $referencedName->getStartPointer(); + $reference->endPointer = $referencedName->getEndPointer(); + $reference->isClass = $referencedName->isClass(); + $reference->isConstant = $referencedName->isConstant(); + $reference->isFunction = $referencedName->isFunction(); + + $references[] = $reference; + } + + foreach (ReferencedNameHelper::getAllReferencedNamesInAttributes($phpcsFile, $openTagPointer) as $referencedName) { + $reference = new \stdClass(); + $reference->source = self::SOURCE_ATTRIBUTE; + $reference->name = $referencedName->getNameAsReferencedInFile(); + $reference->type = $referencedName->getType(); + $reference->startPointer = $referencedName->getStartPointer(); + $reference->endPointer = $referencedName->getEndPointer(); + $reference->isClass = $referencedName->isClass(); + $reference->isConstant = $referencedName->isConstant(); + $reference->isFunction = $referencedName->isFunction(); + + $references[] = $reference; + } + + return $references; + } +} diff --git a/InfinityloopCodingStandard/Sniffs/TypeHints/UnionTypeHintFormatSniff.php b/InfinityloopCodingStandard/Sniffs/TypeHints/UnionTypeHintFormatSniff.php index 3e28550..722eab6 100644 --- a/InfinityloopCodingStandard/Sniffs/TypeHints/UnionTypeHintFormatSniff.php +++ b/InfinityloopCodingStandard/Sniffs/TypeHints/UnionTypeHintFormatSniff.php @@ -4,6 +4,13 @@ namespace InfinityloopCodingStandard\Sniffs\TypeHints; +use \PHP_CodeSniffer\Files\File; +use \SlevomatCodingStandard\Helpers\FunctionHelper; +use \SlevomatCodingStandard\Helpers\PropertyHelper; +use \SlevomatCodingStandard\Helpers\SniffSettingsHelper; +use \SlevomatCodingStandard\Helpers\TokenHelper; +use \SlevomatCodingStandard\Helpers\TypeHint; + /** * https://github.com/slevomat/coding-standard/blob/master/SlevomatCodingStandard/Sniffs/TypeHints/UnionTypeHintFormatSniff.php */ @@ -22,7 +29,7 @@ public function register() : array { return \array_merge( [\T_VARIABLE], - \SlevomatCodingStandard\Helpers\TokenHelper::$functionTokenCodes, + TokenHelper::$functionTokenCodes, ); } @@ -32,20 +39,20 @@ public function register() : array * @param int $pointer */ //@phpcs:ignore Squiz.Commenting.FunctionComment.ScalarTypeHintMissing - public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $pointer) : void + public function process(File $phpcsFile, $pointer) : void { - if (!\SlevomatCodingStandard\Helpers\SniffSettingsHelper::isEnabledByPhpVersion(null, 80000)) { + if (!SniffSettingsHelper::isEnabledByPhpVersion(null, 80000)) { return; } $tokens = $phpcsFile->getTokens(); if ($tokens[$pointer]['code'] === \T_VARIABLE) { - if (!\SlevomatCodingStandard\Helpers\PropertyHelper::isProperty($phpcsFile, $pointer)) { + if (!PropertyHelper::isProperty($phpcsFile, $pointer)) { return; } - $propertyTypeHint = \SlevomatCodingStandard\Helpers\PropertyHelper::findTypeHint($phpcsFile, $pointer); + $propertyTypeHint = PropertyHelper::findTypeHint($phpcsFile, $pointer); if ($propertyTypeHint !== null) { $this->checkTypeHint($phpcsFile, $propertyTypeHint); @@ -54,20 +61,20 @@ public function process(\PHP_CodeSniffer\Files\File $phpcsFile, $pointer) : void return; } - $returnTypeHint = \SlevomatCodingStandard\Helpers\FunctionHelper::findReturnTypeHint($phpcsFile, $pointer); + $returnTypeHint = FunctionHelper::findReturnTypeHint($phpcsFile, $pointer); if ($returnTypeHint !== null) { $this->checkTypeHint($phpcsFile, $returnTypeHint); } - foreach (\SlevomatCodingStandard\Helpers\FunctionHelper::getParametersTypeHints($phpcsFile, $pointer) as $parameterTypeHint) { + foreach (FunctionHelper::getParametersTypeHints($phpcsFile, $pointer) as $parameterTypeHint) { if ($parameterTypeHint !== null) { $this->checkTypeHint($phpcsFile, $parameterTypeHint); } } } - private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \SlevomatCodingStandard\Helpers\TypeHint $typeHint) : void + private function checkTypeHint(File $phpcsFile, TypeHint $typeHint) : void { $tokens = $phpcsFile->getTokens(); @@ -78,7 +85,7 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat $isOneline = true; foreach ( - \SlevomatCodingStandard\Helpers\TokenHelper::findNextAll( + TokenHelper::findNextAll( $phpcsFile, [\T_TYPE_UNION], $typeHint->getStartPointer(), @@ -93,7 +100,7 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat ); } - $nextUnionType = \SlevomatCodingStandard\Helpers\TokenHelper::findNextEffective($phpcsFile, $unionSeparator + 1); + $nextUnionType = TokenHelper::findNextEffective($phpcsFile, $unionSeparator + 1); if ($tokens[$nextUnionType]['line'] === $firstUnionType['line']) { continue; @@ -132,7 +139,7 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat } for ($i = 0; $i < \abs($difference); $i++) { - $token = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious( + $token = TokenHelper::findPrevious( $phpcsFile, [\T_WHITESPACE], $nextUnionType - $i, @@ -154,7 +161,7 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat } if ($isOneline) { - $whitespacePointer = \SlevomatCodingStandard\Helpers\TokenHelper::findNext( + $whitespacePointer = TokenHelper::findNext( $phpcsFile, \T_WHITESPACE, $typeHint->getStartPointer() + 1, @@ -162,7 +169,7 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat ); if ($whitespacePointer !== null) { - $originalTypeHint = \SlevomatCodingStandard\Helpers\TokenHelper::getContent( + $originalTypeHint = TokenHelper::getContent( $phpcsFile, $typeHint->getStartPointer(), $typeHint->getEndPointer(), @@ -216,29 +223,29 @@ private function checkTypeHint(\PHP_CodeSniffer\Files\File $phpcsFile, \Slevomat } private function getTypeHintContentWithoutNull( - \PHP_CodeSniffer\Files\File $phpcsFile, + File $phpcsFile, \SlevomatCodingStandard\Helpers\TypeHint $typeHint, ) : string { $tokens = $phpcsFile->getTokens(); if (\strtolower($tokens[$typeHint->getEndPointer()]['content']) === 'null') { - $previousTypeHintPointer = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious( + $previousTypeHintPointer = TokenHelper::findPrevious( $phpcsFile, - \SlevomatCodingStandard\Helpers\TokenHelper::getOnlyTypeHintTokenCodes(), + TokenHelper::getOnlyTypeHintTokenCodes(), $typeHint->getEndPointer() - 1, ); - return \SlevomatCodingStandard\Helpers\TokenHelper::getContent($phpcsFile, $typeHint->getStartPointer(), $previousTypeHintPointer); + return TokenHelper::getContent($phpcsFile, $typeHint->getStartPointer(), $previousTypeHintPointer); } $content = ''; for ($i = $typeHint->getStartPointer(); $i <= $typeHint->getEndPointer(); $i++) { if (\strtolower($tokens[$i]['content']) === 'null') { - $i = \SlevomatCodingStandard\Helpers\TokenHelper::findNext( + $i = TokenHelper::findNext( $phpcsFile, - \SlevomatCodingStandard\Helpers\TokenHelper::getOnlyTypeHintTokenCodes(), + TokenHelper::getOnlyTypeHintTokenCodes(), $i + 1, ); } @@ -250,7 +257,7 @@ private function getTypeHintContentWithoutNull( } private function fixTypeHint( - \PHP_CodeSniffer\Files\File $phpcsFile, + File $phpcsFile, \SlevomatCodingStandard\Helpers\TypeHint $typeHint, string $fixedTypeHint, ) : void diff --git a/InfinityloopCodingStandard/Sniffs/WhiteSpace/MemberVarSpacingSniff.php b/InfinityloopCodingStandard/Sniffs/WhiteSpace/MemberVarSpacingSniff.php index a3c9dfc..69590e7 100644 --- a/InfinityloopCodingStandard/Sniffs/WhiteSpace/MemberVarSpacingSniff.php +++ b/InfinityloopCodingStandard/Sniffs/WhiteSpace/MemberVarSpacingSniff.php @@ -4,6 +4,9 @@ namespace InfinityloopCodingStandard\Sniffs\WhiteSpace; +use \PHP_CodeSniffer\Files\File; +use \PHP_CodeSniffer\Util\Tokens; + class MemberVarSpacingSniff extends \PHP_CodeSniffer\Sniffs\AbstractVariableSniff { public int $spacing = 1; @@ -11,11 +14,11 @@ class MemberVarSpacingSniff extends \PHP_CodeSniffer\Sniffs\AbstractVariableSnif public bool $ignoreFirstMemberVar = false; //@phpcs:ignore Squiz.Commenting.FunctionComment.ScalarTypeHintMissing - protected function processMemberVar(\PHP_CodeSniffer\Files\File $phpcsFile, $stackPtr) : ?int + protected function processMemberVar(File $phpcsFile, $stackPtr) : ?int { $tokens = $phpcsFile->getTokens(); - $validPrefixes = \PHP_CodeSniffer\Util\Tokens::$methodPrefixes; + $validPrefixes = Tokens::$methodPrefixes; $validPrefixes[] = \T_VAR; $startOfStatement = $phpcsFile->findPrevious($validPrefixes, $stackPtr - 1, null, false, null, true); @@ -32,9 +35,9 @@ protected function processMemberVar(\PHP_CodeSniffer\Files\File $phpcsFile, $sta $start = $startOfStatement; $prev = $phpcsFile->findPrevious($ignore, $startOfStatement - 1, null, true); - if (isset(\PHP_CodeSniffer\Util\Tokens::$commentTokens[$tokens[$prev]['code']]) === true) { + if (isset(Tokens::$commentTokens[$tokens[$prev]['code']]) === true) { // Assume the comment belongs to the member var if it is on a line by itself. - $prevContent = $phpcsFile->findPrevious(\PHP_CodeSniffer\Util\Tokens::$emptyTokens, $prev - 1, null, true); + $prevContent = $phpcsFile->findPrevious(Tokens::$emptyTokens, $prev - 1, null, true); if ($tokens[$prevContent]['line'] !== $tokens[$prev]['line']) { // Check the spacing, but then skip it. @@ -74,7 +77,7 @@ protected function processMemberVar(\PHP_CodeSniffer\Files\File $phpcsFile, $sta // There needs to be n blank lines before the var, not counting comments. if ($start === $startOfStatement) { // No comment found. - $first = $phpcsFile->findFirstOnLine(\PHP_CodeSniffer\Util\Tokens::$emptyTokens, $start, true); + $first = $phpcsFile->findFirstOnLine(Tokens::$emptyTokens, $start, true); if ($first === false) { $first = $start; @@ -82,8 +85,8 @@ protected function processMemberVar(\PHP_CodeSniffer\Files\File $phpcsFile, $sta } elseif ($tokens[$start]['code'] === \T_DOC_COMMENT_CLOSE_TAG) { $first = $tokens[$start]['comment_opener']; } else { - $first = $phpcsFile->findPrevious(\PHP_CodeSniffer\Util\Tokens::$emptyTokens, $start - 1, null, true); - $first = $phpcsFile->findNext(\PHP_CodeSniffer\Util\Tokens::$commentTokens, $first + 1); + $first = $phpcsFile->findPrevious(Tokens::$emptyTokens, $start - 1, null, true); + $first = $phpcsFile->findNext(Tokens::$commentTokens, $first + 1); } // Determine if this is the first member var. @@ -103,7 +106,7 @@ protected function processMemberVar(\PHP_CodeSniffer\Files\File $phpcsFile, $sta } if ($tokens[$prev]['code'] === \T_OPEN_CURLY_BRACKET - && isset(\PHP_CodeSniffer\Util\Tokens::$ooScopeTokens[$tokens[$tokens[$prev]['scope_condition']]['code']]) === true + && isset(Tokens::$ooScopeTokens[$tokens[$tokens[$prev]['scope_condition']]['code']]) === true ) { $errorMsg = 'Expected %s blank line(s) before first member var; %s found'; $errorCode = 'FirstIncorrect'; diff --git a/InfinityloopCodingStandard/ruleset.xml b/InfinityloopCodingStandard/ruleset.xml index a939741..14467e4 100644 --- a/InfinityloopCodingStandard/ruleset.xml +++ b/InfinityloopCodingStandard/ruleset.xml @@ -503,4 +503,10 @@ + + + + + + diff --git a/README.md b/README.md index e89517e..e1beb7e 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ All other necessary sniffs to enforce remaining PSR12 rules are included. #### InfinityloopCodingStandard.Classes.FinalClassVisibility :wrench: -When class is final and doesnt extend any other class, it's safe to change visibility of all protected functions/properties to private. +When class is final and doesn't extend any other class, it's safe to change visibility of all protected functions/properties to private. #### InfinityloopCodingStandard.Namespaces.UseDoesStartWithBackslash :wrench: @@ -86,9 +86,17 @@ Improved version of Slevomat UnionTypeHintFormat with added formatting of multil Space constructor arguments one per line when Constructor Property Promotion is used +#### InfinityloopCodingStandard.Namespaces.ReferenceUsedNamesAfterUsage :wrench: + +Specialized version of Slevomat ReferenceUsedNamesOnlySniff with added rules when the sniff should reference by FQN or Use statement. + +Sniff provides the following settings: +- count - Minimum number of occurrences after which the class will get imported via Use statement. +- length - The maximum length of the class up to which it will be used via FQN, if this length is exceeded it will be imported via Use statement. + ### Slevomat sniffs -Detailed list of Slevomat sniffs with configured settings. Some sniffs are not included, either because we dont find them helpful, the are too strict, or collide with their counter-sniff (require/disallow pairs). +Detailed list of Slevomat sniffs with configured settings. Some sniffs are not included, either because we don't find them helpful, they are too strict, or collide with their counter-sniff (require/disallow pairs). #### Functional diff --git a/composer.json b/composer.json index f5327f2..ae6d375 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,8 @@ "bamarni/composer-bin-plugin": "^1.2" }, "scripts": { - "codestyle": "phpcs --standard=InfinityloopCodingStandard --extensions=php InfinityloopCodingStandard" + "codestyle": "phpcs --standard=InfinityloopCodingStandard --extensions=php InfinityloopCodingStandard", + "codestyle-fix": "phpcbf --standard=InfinityloopCodingStandard --extensions=php InfinityloopCodingStandard" }, "autoload": { "psr-4": { From f30101c7769962a316112700cded2464e33a3843 Mon Sep 17 00:00:00 2001 From: Vossik Date: Tue, 9 Nov 2021 13:03:54 +0100 Subject: [PATCH 2/3] ignore built-in functions & constants --- .../Namespaces/ReferenceUsedNamesAfterUsageSniff.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php index e8f51c1..75f15b8 100644 --- a/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php +++ b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php @@ -130,13 +130,15 @@ public function process(File $phpcsFile, $openTagPointer) : void } } - //Skip functions and constants - if ($reference->isFunction || $reference->isConstant) { + if ($reference->isClass === true && !NamespaceHelper::hasNamespace($name) && $isFullyQualified) { continue; } - //Ignore stdClass - if ($reference->isClass === true && $canonicalName === 'stdClass') { + if ($reference->isFunction === true && !NamespaceHelper::hasNamespace($name) && $isFullyQualified) { + continue; + } + + if ($reference->isConstant === true && !NamespaceHelper::hasNamespace($name) && $isFullyQualified) { continue; } From 174b8ccc46b350b2d7c4930b5252a20076e62691 Mon Sep 17 00:00:00 2001 From: Vossik Date: Tue, 9 Nov 2021 21:18:32 +0100 Subject: [PATCH 3/3] Improved ReferenceUsedNamesAfterUsageSniff --- .../ReferenceUsedNamesAfterUsageSniff.php | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php index 75f15b8..bb77768 100644 --- a/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php +++ b/InfinityloopCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesAfterUsageSniff.php @@ -10,8 +10,6 @@ use \SlevomatCodingStandard\Helpers\AnnotationHelper; use \SlevomatCodingStandard\Helpers\ClassHelper; use \SlevomatCodingStandard\Helpers\CommentHelper; -use \SlevomatCodingStandard\Helpers\ConstantHelper; -use \SlevomatCodingStandard\Helpers\FunctionHelper; use \SlevomatCodingStandard\Helpers\NamespaceHelper; use \SlevomatCodingStandard\Helpers\ReferencedNameHelper; use \SlevomatCodingStandard\Helpers\StringHelper; @@ -54,9 +52,7 @@ public function process(File $phpcsFile, $openTagPointer) : void } $tokens = $phpcsFile->getTokens(); - $references = $this->getReferences($phpcsFile, $openTagPointer); - $definedClassesIndex = []; foreach (ClassHelper::getAllNames($phpcsFile) as $definedClassPointer => $definedClassName) { @@ -67,11 +63,6 @@ public function process(File $phpcsFile, $openTagPointer) : void ); } - $definedFunctionsIndex = \array_flip(\array_map(static function (string $functionName) : string { - return \strtolower($functionName); - }, FunctionHelper::getAllFunctionNames($phpcsFile))); - $definedConstantsIndex = \array_flip(ConstantHelper::getAllNames($phpcsFile)); - $classReferencesIndex = []; $classReferences = \array_filter($references, static function (\stdClass $reference) : bool { return $reference->source === self::SOURCE_CODE && $reference->isClass; @@ -106,29 +97,6 @@ public function process(File $phpcsFile, $openTagPointer) : void $startPointer = $reference->startPointer; $canonicalName = NamespaceHelper::normalizeToCanonicalName($name); $isFullyQualified = NamespaceHelper::isFullyQualifiedName($name); - $isGlobalFallback = !$isFullyQualified - && !NamespaceHelper::hasNamespace($name) - && $namespacePointers !== [] - && !\array_key_exists(UseStatement::getUniqueId($reference->type, $name), $useStatements); - $isGlobalFunctionFallback = false; - - if ($reference->isFunction && $isGlobalFallback) { - $isGlobalFunctionFallback = !\array_key_exists(\strtolower($reference->name), $definedFunctionsIndex) && \function_exists( - $reference->name, - ); - } - - $isGlobalConstantFallback = false; - - if ($reference->isConstant && $isGlobalFallback) { - $isGlobalConstantFallback = !\array_key_exists($reference->name, $definedConstantsIndex) && \defined($reference->name); - } - - if ($isFullyQualified || $isGlobalFunctionFallback || $isGlobalConstantFallback) { - if ($isFullyQualified && !$this->isRequiredToBeUsed($name)) { - continue; - } - } if ($reference->isClass === true && !NamespaceHelper::hasNamespace($name) && $isFullyQualified) { continue; @@ -220,8 +188,6 @@ public function process(File $phpcsFile, $openTagPointer) : void $referenceErrors[] = (object) [ 'reference' => $reference, 'canonicalName' => $canonicalName, - 'isGlobalConstantFallback' => $isGlobalConstantFallback, - 'isGlobalFunctionFallback' => $isGlobalFunctionFallback, 'reason' => $reason, ]; } @@ -243,14 +209,9 @@ public function process(File $phpcsFile, $openTagPointer) : void $reference = $referenceData->reference; $startPointer = $reference->startPointer; $canonicalName = $referenceData->canonicalName; - $nameToReference = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($reference->name); - $canonicalNameToReference = $reference->isConstant - ? $nameToReference - : \strtolower($nameToReference); - $isGlobalConstantFallback = $referenceData->isGlobalConstantFallback; - $isGlobalFunctionFallback = $referenceData->isGlobalFunctionFallback; - $useStatements = UseStatementHelper::getUseStatementsForPointer($phpcsFile, $reference->startPointer); + [$nameToReference, $isConflicting] = $this->getNormalizedClassName($reference->name, $useStatements); + $canonicalNameToReference = \strtolower($nameToReference); $canBeFixed = \array_reduce( $alreadyAddedUses[$reference->type], @@ -276,8 +237,6 @@ static function (bool $carry, string $use) use ($canonicalName) : bool { && \array_key_exists($canonicalNameToReference, $classReferencesIndex) && $canonicalName !== NamespaceHelper::normalizeToCanonicalName($classReferencesIndex[$canonicalNameToReference]) ) - || ($reference->isFunction && \array_key_exists($canonicalNameToReference, $definedFunctionsIndex)) - || ($reference->isConstant && \array_key_exists($canonicalNameToReference, $definedConstantsIndex)) ) { $canBeFixed = false; } @@ -301,12 +260,11 @@ static function (bool $carry, string $use) use ($canonicalName) : bool { } $label = \sprintf('Class %s', $reference->name); - $errorCode = $isGlobalConstantFallback || $isGlobalFunctionFallback - ? self::CODE_REFERENCE_VIA_FALLBACK_GLOBAL_NAME - : self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME; - $errorMessage = $isGlobalConstantFallback || $isGlobalFunctionFallback - ? \sprintf('%s should not be referenced via a fallback global name, but via a use statement ' . $referenceData->reason, $label) - : \sprintf('%s should not be referenced via a fully qualified name, but via a use statement ' . $referenceData->reason, $label); + $errorCode = self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME; + $errorMessage = \sprintf( + '%s should not be referenced via a fully qualified name, but via a use statement ' . $referenceData->reason, + $label, + ); if (!$canBeFixed) { $phpcsFile->addError($errorMessage, $startPointer, $errorCode); @@ -347,7 +305,15 @@ static function (bool $carry, string $use) use ($canonicalName) : bool { : ''; $phpcsFile->fixer->addNewline($useStatementPlacePointer); - $phpcsFile->fixer->addContent($useStatementPlacePointer, \sprintf('use %s%s;', $useTypeFormatted, $canonicalName)); + + if ($isConflicting === true) { + $phpcsFile->fixer->addContent( + $useStatementPlacePointer, + \sprintf('use %s%s as %s;', $useTypeFormatted, $canonicalName, $nameToReference), + ); + } else { + $phpcsFile->fixer->addContent($useStatementPlacePointer, \sprintf('use %s%s;', $useTypeFormatted, $canonicalName)); + } $alreadyAddedUses[$reference->type][] = $canonicalName; } @@ -388,11 +354,6 @@ static function (bool $carry, string $use) use ($canonicalName) : bool { $phpcsFile->fixer->endChangeset(); } - private function isRequiredToBeUsed(string $name) : bool - { - return true; - } - private function getUseStatementPlacePointer(\PHP_CodeSniffer\Files\File $phpcsFile, int $openTagPointer, array $useStatements) : int { if (\count($useStatements) !== 0) { @@ -475,4 +436,43 @@ private function getReferences(\PHP_CodeSniffer\Files\File $phpcsFile, int $open return $references; } + + private function getNormalizedClassName(string $name, array $useStatements) : array + { + $unqualifiedName = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($name); + + foreach ($useStatements as $useStatement) { + $useStatementUnqualified = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($useStatement->getFullyQualifiedTypeName()); + + if ($unqualifiedName !== $useStatementUnqualified) { + continue; + } + + $nameSplit = \explode('\\', \ltrim($name, '\\')); + $useStatementSplit = \explode('\\', \ltrim($useStatement->getFullyQualifiedTypeName(), '\\')); + + $i = 0; + $toUse = null; + + foreach ($nameSplit as $value) { + if (!isset($useStatementSplit[$i])) { + break; + } + + if (\substr($value, 0, 1) !== \substr($useStatementSplit[$i], 0, 1)) { + $toUse = \substr($value, 0, 1); + + break; + } + + $i++; + } + + return $toUse === null + ? [$unqualifiedName, false] + : [$toUse . $unqualifiedName, true]; + } + + return [$unqualifiedName, false]; + } }