From 1b1b75dca62c269c207e52d5cc72765e92477e39 Mon Sep 17 00:00:00 2001 From: Jaroslav Hanslik Date: Thu, 2 Feb 2017 18:55:42 +0100 Subject: [PATCH] Fixable ReferenceThrowableOnlySniff --- .../Helpers/ReferencedName.php | 19 +++++-- .../Helpers/ReferencedNameHelper.php | 2 +- .../ReferenceThrowableOnlySniff.php | 6 +- .../FullyQualifiedExceptionsSniff.php | 2 +- .../ReferenceUsedNamesOnlySniff.php | 56 ++++++++++++++++--- .../Sniffs/Namespaces/UnusedUsesSniff.php | 2 +- .../ReferenceUsedNamesOnlySniffTest.php | 22 ++++++++ ...leReferenceViaFullyQualifiedName.fixed.php | 27 +++++++++ .../fixableReferenceViaFullyQualifiedName.php | 23 ++++++++ ...llyQualifiedNameWithoutNamespace.fixed.php | 28 ++++++++++ ...eViaFullyQualifiedNameWithoutNamespace.php | 28 ++++++++++ 11 files changed, 196 insertions(+), 19 deletions(-) create mode 100644 tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedName.fixed.php create mode 100644 tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedName.php create mode 100644 tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedNameWithoutNamespace.fixed.php create mode 100644 tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedNameWithoutNamespace.php diff --git a/SlevomatCodingStandard/Helpers/ReferencedName.php b/SlevomatCodingStandard/Helpers/ReferencedName.php index 7f24e57ed..bcdd28638 100644 --- a/SlevomatCodingStandard/Helpers/ReferencedName.php +++ b/SlevomatCodingStandard/Helpers/ReferencedName.php @@ -13,15 +13,19 @@ class ReferencedName private $nameAsReferencedInFile; /** @var int */ - private $pointer; + private $startPointer; + + /** @var int */ + private $endPointer; /** @var string */ private $type; - public function __construct(string $nameAsReferencedInFile, int $pointer, string $type) + public function __construct(string $nameAsReferencedInFile, int $startPointer, int $endPointer, string $type) { $this->nameAsReferencedInFile = $nameAsReferencedInFile; - $this->pointer = $pointer; + $this->startPointer = $startPointer; + $this->endPointer = $endPointer; $this->type = $type; } @@ -30,9 +34,14 @@ public function getNameAsReferencedInFile(): string return $this->nameAsReferencedInFile; } - public function getPointer(): int + public function getStartPointer(): int + { + return $this->startPointer; + } + + public function getEndPointer(): int { - return $this->pointer; + return $this->endPointer; } public function isConstant(): bool diff --git a/SlevomatCodingStandard/Helpers/ReferencedNameHelper.php b/SlevomatCodingStandard/Helpers/ReferencedNameHelper.php index 870ad701e..087adffcf 100644 --- a/SlevomatCodingStandard/Helpers/ReferencedNameHelper.php +++ b/SlevomatCodingStandard/Helpers/ReferencedNameHelper.php @@ -138,7 +138,7 @@ private static function createAllReferencedNames(\PHP_CodeSniffer_File $phpcsFil } } } - $types[] = new ReferencedName(TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer), $nameStartPointer, $type); + $types[] = new ReferencedName(TokenHelper::getContent($phpcsFile, $nameStartPointer, $nameEndPointer), $nameStartPointer, $nameEndPointer, $type); $beginSearchAtPointer = $nameEndPointer + 1; } return $types; diff --git a/SlevomatCodingStandard/Sniffs/Exceptions/ReferenceThrowableOnlySniff.php b/SlevomatCodingStandard/Sniffs/Exceptions/ReferenceThrowableOnlySniff.php index 6c1694dba..852c94190 100644 --- a/SlevomatCodingStandard/Sniffs/Exceptions/ReferenceThrowableOnlySniff.php +++ b/SlevomatCodingStandard/Sniffs/Exceptions/ReferenceThrowableOnlySniff.php @@ -39,12 +39,12 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) $phpcsFile, $referencedName->getNameAsReferencedInFile(), $useStatements, - $referencedName->getPointer() + $referencedName->getStartPointer() ); if ($resolvedName !== '\\Exception') { continue; } - $previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $referencedName->getPointer() - 1); + $previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $referencedName->getStartPointer() - 1); if (in_array($tokens[$previousPointer]['code'], [T_EXTENDS, T_NEW, T_INSTANCEOF], true)) { continue; // allow \Exception in extends and instantiating it } @@ -65,7 +65,7 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) } $phpcsFile->addError( $message, - $referencedName->getPointer(), + $referencedName->getStartPointer(), self::CODE_REFERENCED_GENERAL_EXCEPTION ); } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php index 5aad9fb16..b1cb2808d 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/FullyQualifiedExceptionsSniff.php @@ -70,7 +70,7 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) $referencedNames = ReferencedNameHelper::getAllReferencedNames($phpcsFile, $openTagPointer); $useStatements = UseStatementHelper::getUseStatements($phpcsFile, $openTagPointer); foreach ($referencedNames as $referencedName) { - $pointer = $referencedName->getPointer(); + $pointer = $referencedName->getStartPointer(); $name = $referencedName->getNameAsReferencedInFile(); $normalizedName = UseStatement::normalizedNameAsReferencedInFile($name); if (isset($useStatements[$normalizedName]) && $referencedName->hasSameUseStatementType($useStatements[$normalizedName])) { diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php index 4f4c2454a..cc5ed0204 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/ReferenceUsedNamesOnlySniff.php @@ -7,6 +7,7 @@ use SlevomatCodingStandard\Helpers\SniffSettingsHelper; use SlevomatCodingStandard\Helpers\StringHelper; use SlevomatCodingStandard\Helpers\TokenHelper; +use SlevomatCodingStandard\Helpers\UseStatementHelper; class ReferenceUsedNamesOnlySniff implements \PHP_CodeSniffer_Sniff { @@ -122,10 +123,11 @@ private function getFullyQualifiedKeywords(): array public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) { $tokens = $phpcsFile->getTokens(); + $referencedNames = ReferencedNameHelper::getAllReferencedNames($phpcsFile, $openTagPointer); foreach ($referencedNames as $referencedName) { $name = $referencedName->getNameAsReferencedInFile(); - $pointer = $referencedName->getPointer(); + $nameStartPointer = $referencedName->getStartPointer(); $canonicalName = NamespaceHelper::normalizeToCanonicalName($name); if (NamespaceHelper::isFullyQualifiedName($name)) { $isExceptionByName = StringHelper::endsWith($name, 'Exception') @@ -144,23 +146,61 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) $previousKeywordPointer = TokenHelper::findPreviousExcluding($phpcsFile, array_merge( TokenHelper::$nameTokenCodes, [T_WHITESPACE, T_COMMA] - ), $pointer - 1); + ), $nameStartPointer - 1); if ( !in_array($tokens[$previousKeywordPointer]['code'], $this->getFullyQualifiedKeywords(), true) ) { if ( !NamespaceHelper::hasNamespace($name) - && NamespaceHelper::findCurrentNamespaceName($phpcsFile, $pointer) === null + && NamespaceHelper::findCurrentNamespaceName($phpcsFile, $nameStartPointer) === null ) { - $phpcsFile->addError(sprintf( + $fix = $phpcsFile->addFixableError(sprintf( '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', $name - ), $pointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE); + ), $nameStartPointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE); + if ($fix) { + $phpcsFile->fixer->beginChangeset(); + $phpcsFile->fixer->replaceToken($nameStartPointer, substr($tokens[$nameStartPointer]['content'], 1)); + $phpcsFile->fixer->endChangeset(); + } } else { - $phpcsFile->addError(sprintf( + $fix = $phpcsFile->addFixableError(sprintf( 'Type %s should not be referenced via a fully qualified name, but via a use statement', $name - ), $pointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME); + ), $nameStartPointer, self::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME); + if ($fix) { + $useStatements = UseStatementHelper::getUseStatements($phpcsFile, $openTagPointer); + if (count($useStatements) === 0) { + $namespacePointer = $phpcsFile->findNext(T_NAMESPACE, $openTagPointer); + $useStatementPlacePointer = $phpcsFile->findNext([T_SEMICOLON, T_OPEN_CURLY_BRACKET], $namespacePointer + 1); + } else { + $lastUseStatement = array_values($useStatements)[count($useStatements) - 1]; + $useStatementPlacePointer = $phpcsFile->findNext(T_SEMICOLON, $lastUseStatement->getPointer() + 1); + } + + $phpcsFile->fixer->beginChangeset(); + + for ($i = $referencedName->getStartPointer(); $i <= $referencedName->getEndPointer(); $i++) { + $phpcsFile->fixer->replaceToken($i, ''); + } + + $phpcsFile->fixer->addContent($referencedName->getStartPointer(), NamespaceHelper::getUnqualifiedNameFromFullyQualifiedName($name)); + + $alreadyUsed = false; + foreach ($useStatements as $useStatement) { + if ($useStatement->getFullyQualifiedTypeName() === $canonicalName) { + $alreadyUsed = true; + break; + } + } + + if (!$alreadyUsed) { + $phpcsFile->fixer->addNewline($useStatementPlacePointer); + $phpcsFile->fixer->addContent($useStatementPlacePointer, sprintf('use %s;', $canonicalName)); + } + + $phpcsFile->fixer->endChangeset(); + } } } } @@ -169,7 +209,7 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) $phpcsFile->addError(sprintf( 'Partial use statements are not allowed, but referencing %s found', $name - ), $pointer, self::CODE_PARTIAL_USE); + ), $nameStartPointer, self::CODE_PARTIAL_USE); } } } diff --git a/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php b/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php index 5b6d25e30..5b6c6fe20 100644 --- a/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php +++ b/SlevomatCodingStandard/Sniffs/Namespaces/UnusedUsesSniff.php @@ -38,7 +38,7 @@ public function process(\PHP_CodeSniffer_File $phpcsFile, $openTagPointer) foreach ($referencedNames as $referencedName) { $name = $referencedName->getNameAsReferencedInFile(); - $pointer = $referencedName->getPointer(); + $pointer = $referencedName->getStartPointer(); $nameParts = NamespaceHelper::getNameParts($name); $nameAsReferencedInFile = $nameParts[0]; $normalizedNameAsReferencedInFile = UseStatement::normalizedNameAsReferencedInFile($nameAsReferencedInFile); diff --git a/tests/Sniffs/Namespaces/ReferenceUsedNamesOnlySniffTest.php b/tests/Sniffs/Namespaces/ReferenceUsedNamesOnlySniffTest.php index ce821cda2..6be27ca7e 100644 --- a/tests/Sniffs/Namespaces/ReferenceUsedNamesOnlySniffTest.php +++ b/tests/Sniffs/Namespaces/ReferenceUsedNamesOnlySniffTest.php @@ -529,4 +529,26 @@ public function testIgnoredNamesWithAllowFullyQualifiedExceptionsInNamespace() $this->assertNoSniffErrorInFile($report); } + + public function testFixableReferenceViaFullyQualifiedName() + { + $report = $this->checkFile(__DIR__ . '/data/fixableReferenceViaFullyQualifiedName.php', [ + 'fullyQualifiedKeywords' => ['T_EXTENDS'], + 'allowFullyQualifiedExceptions' => true, + ], [ReferenceUsedNamesOnlySniff::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME]); + $this->assertAllFixedInFile($report); + } + + public function testFixableReferenceViaFullyQualifiedNameWithoutNamespace() + { + $report = $this->checkFile(__DIR__ . '/data/fixableReferenceViaFullyQualifiedNameWithoutNamespace.php', [ + 'fullyQualifiedKeywords' => ['T_IMPLEMENTS'], + 'allowFullyQualifiedExceptions' => false, + 'specialExceptionNames' => [ + 'BarErrorX', + ], + ], [ReferenceUsedNamesOnlySniff::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME_WITHOUT_NAMESPACE]); + $this->assertAllFixedInFile($report); + } + } diff --git a/tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedName.fixed.php b/tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedName.fixed.php new file mode 100644 index 000000000..d3dd31aa3 --- /dev/null +++ b/tests/Sniffs/Namespaces/data/fixableReferenceViaFullyQualifiedName.fixed.php @@ -0,0 +1,27 @@ +