Skip to content

Commit

Permalink
ReferenceUsedNamesOnlySniff: Less colisions with AlphabeticallySorted…
Browse files Browse the repository at this point in the history
…UsesSniff
  • Loading branch information
kukulich committed Jun 19, 2020
1 parent c4e2878 commit cf0627a
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 97 deletions.
5 changes: 5 additions & 0 deletions SlevomatCodingStandard/Helpers/NamespaceHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public static function getNameParts(string $name): array
return explode(self::NAMESPACE_SEPARATOR, $name);
}

public static function getLastNamePart(string $name): string
{
return array_slice(self::getNameParts($name), -1)[0];
}

public static function getName(File $phpcsFile, int $namespacePointer): string
{
/** @var int $namespaceNameStartPointer */
Expand Down
233 changes: 136 additions & 97 deletions SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use function array_key_exists;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_values;
use function constant;
use function count;
Expand Down Expand Up @@ -170,6 +171,8 @@ public function process(File $phpcsFile, $openTagPointer): void

$namespacePointers = NamespaceHelper::getAllNamespacesPointers($phpcsFile);

$referenceErrors = [];

foreach ($references as $reference) {
$useStatements = UseStatementHelper::getUseStatementsForPointer($phpcsFile, $reference->startPointer);

Expand Down Expand Up @@ -307,122 +310,158 @@ public function process(File $phpcsFile, $openTagPointer): void
continue;
}

$nameToReference = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($name);
$canonicalNameToReference = $reference->isConstant ? $nameToReference : strtolower($nameToReference);
$referenceErrors[] = (object) [
'reference' => $reference,
'canonicalName' => $canonicalName,
'isGlobalConstantFallback' => $isGlobalConstantFallback,
'isGlobalFunctionFallback' => $isGlobalFunctionFallback,
];
}
}
} elseif (!$this->allowPartialUses) {
if (NamespaceHelper::isQualifiedName($name)) {
$phpcsFile->addError(sprintf(
'Partial use statements are not allowed, but referencing %s found.',
$name
), $startPointer, self::CODE_PARTIAL_USE);
}
}
}

$canBeFixed = true;
foreach ($useStatements as $useStatement) {
if ($useStatement->getType() !== $reference->type) {
continue;
}
if (count($referenceErrors) === 0) {
return;
}

if ($useStatement->getFullyQualifiedTypeName() === $canonicalName) {
continue;
}
$alreadyAddedUses = [
UseStatement::TYPE_DEFAULT => [],
UseStatement::TYPE_FUNCTION => [],
UseStatement::TYPE_CONSTANT => [],
];

if (!(
$useStatement->getCanonicalNameAsReferencedInFile() === $canonicalNameToReference
|| (
$reference->isClass
&& array_key_exists($canonicalNameToReference, $definedClassesIndex)
&& $canonicalName !== NamespaceHelper::normalizeToCanonicalName($definedClassesIndex[$canonicalNameToReference])
)
|| ($reference->isFunction && array_key_exists($canonicalNameToReference, $definedFunctionsIndex))
|| ($reference->isConstant && array_key_exists($canonicalNameToReference, $definedConstantsIndex))
)) {
continue;
}
$phpcsFile->fixer->beginChangeset();

$canBeFixed = false;
break;
}
foreach ($referenceErrors as $referenceData) {
$reference = $referenceData->reference;
/** @var int $startPointer */
$startPointer = $reference->startPointer;
$canonicalName = $referenceData->canonicalName;
$nameToReference = NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($reference->name);
$canonicalNameToReference = $reference->isConstant ? $nameToReference : strtolower($nameToReference);
$isGlobalConstantFallback = $referenceData->isGlobalConstantFallback;
$isGlobalFunctionFallback = $referenceData->isGlobalFunctionFallback;

$label = sprintf($reference->isConstant ? 'Constant %s' : ($reference->isFunction ? 'Function %s()' : 'Class %s'), $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.', $label)
: sprintf('%s should not be referenced via a fully qualified name, but via a use statement.', $label);
if ($canBeFixed) {
$fix = $phpcsFile->addFixableError($errorMessage, $startPointer, $errorCode);
} else {
$phpcsFile->addError($errorMessage, $startPointer, $errorCode);
$fix = false;
}
$useStatements = UseStatementHelper::getUseStatementsForPointer($phpcsFile, $reference->startPointer);

if ($fix) {
$addUse = true;
$canBeFixed = array_reduce($alreadyAddedUses[$reference->type], static function (bool $carry, string $use) use ($canonicalName): bool {
return NamespaceHelper::getLastNamePart($use) === NamespaceHelper::getLastNamePart($canonicalName)
? false
: $carry;
}, true);

if (
$reference->isClass
&& array_key_exists($canonicalNameToReference, $definedClassesIndex)
) {
$addUse = false;
}
foreach ($useStatements as $useStatement) {
if ($useStatement->getType() !== $reference->type) {
continue;
}

foreach ($useStatements as $useStatement) {
if (
$useStatement->getType() !== $reference->type
|| $useStatement->getFullyQualifiedTypeName() !== $canonicalName
) {
continue;
}
if ($useStatement->getFullyQualifiedTypeName() === $canonicalName) {
continue;
}

$nameToReference = $useStatement->getNameAsReferencedInFile();
$addUse = false;
break;
}
if (!(
$useStatement->getCanonicalNameAsReferencedInFile() === $canonicalNameToReference
|| (
$reference->isClass
&& array_key_exists($canonicalNameToReference, $definedClassesIndex)
&& $canonicalName !== NamespaceHelper::normalizeToCanonicalName($definedClassesIndex[$canonicalNameToReference])
)
|| ($reference->isFunction && array_key_exists($canonicalNameToReference, $definedFunctionsIndex))
|| ($reference->isConstant && array_key_exists($canonicalNameToReference, $definedConstantsIndex))
)) {
continue;
}

$phpcsFile->fixer->beginChangeset();
$canBeFixed = false;
break;
}

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);
} else {
$phpcsFile->fixer->replaceToken($startPointer, $nameToReference);
}
$label = sprintf($reference->isConstant ? 'Constant %s' : ($reference->isFunction ? 'Function %s()' : '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.', $label)
: sprintf('%s should not be referenced via a fully qualified name, but via a use statement.', $label);

for ($i = $startPointer + 1; $i <= $reference->endPointer; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
if (!$canBeFixed) {
$phpcsFile->addError($errorMessage, $startPointer, $errorCode);
continue;
}

if ($addUse) {
$useStatementPlacePointer = $this->getUseStatementPlacePointer($phpcsFile, $openTagPointer, $useStatements);
$fix = $phpcsFile->addFixableError($errorMessage, $startPointer, $errorCode);

$useTypeName = UseStatement::getTypeName($reference->type);
$useTypeFormatted = $useTypeName !== null ? sprintf('%s ', $useTypeName) : '';
if (!$fix) {
continue;
}

$phpcsFile->fixer->addNewline($useStatementPlacePointer);
$phpcsFile->fixer->addContent($useStatementPlacePointer, sprintf('use %s%s;', $useTypeFormatted, $canonicalName));
}
$addUse = !in_array($canonicalName, $alreadyAddedUses[$reference->type], true);

$phpcsFile->fixer->endChangeset();
}
}
}
} elseif (!$this->allowPartialUses) {
if (NamespaceHelper::isQualifiedName($name)) {
$phpcsFile->addError(sprintf(
'Partial use statements are not allowed, but referencing %s found.',
$name
), $startPointer, self::CODE_PARTIAL_USE);
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;
break;
}

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);
} else {
$phpcsFile->fixer->replaceToken($startPointer, $nameToReference);
}

for ($i = $startPointer + 1; $i <= $reference->endPointer; $i++) {
$phpcsFile->fixer->replaceToken($i, '');
}
}

$phpcsFile->fixer->endChangeset();
}

/**
Expand Down

0 comments on commit cf0627a

Please sign in to comment.