Skip to content

Commit

Permalink
Options to ignore type names that look like exceptions but are not (c…
Browse files Browse the repository at this point in the history
…loses #52)
  • Loading branch information
ondrejmirtes committed Aug 10, 2016
1 parent fd9de4b commit 53c24d3
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 51 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ try {

Exceptions with different names can be configured in `specialExceptionNames` property.

If your codebase uses classes that look like exceptions (because they have `Exception` or `Error` suffixes) but aren't,
you can add them to `ignoredNames` property and the sniff won't enforce them to be fully qualified. Classes with `Error`
suffix has to be added to ignored only if they are in the root namespace (like `LibXMLError`).

#### SlevomatCodingStandard.Namespaces.MultipleUsesPerLine

Prohibits multiple uses separated by commas:
Expand All @@ -126,7 +130,7 @@ Enforces to use all referenced names with configurable omissions:

`fullyQualifiedKeywords` - allows fully qualified names after certain keywords. Useful in tandem with FullyQualifiedClassNameAfterKeyword sniff.

`allowFullyQualifiedExceptions` & `specialExceptionNames` - allows fully qualified exceptions. Useful in tandem with FullyQualifiedExceptions sniff.
`allowFullyQualifiedExceptions`, `specialExceptionNames` & `ignoredNames` - allows fully qualified exceptions. Useful in tandem with FullyQualifiedExceptions sniff.

`allowPartialUses` - allows using and referencing whole namespaces:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ class FullyQualifiedExceptionsSniff implements \PHP_CodeSniffer_Sniff
/** @var string[] */
private $normalizedSpecialExceptionNames;

/** @var string[] */
public $ignoredNames = [];

/** @var string[] */
private $normalizedIgnoredNames;

/**
* @return integer[]
*/
Expand All @@ -43,6 +49,18 @@ private function getSpecialExceptionNames()
return $this->normalizedSpecialExceptionNames;
}

/**
* @return string[]
*/
private function getIgnoredNames()
{
if ($this->normalizedIgnoredNames === null) {
$this->normalizedIgnoredNames = SniffSettingsHelper::normalizeArray($this->ignoredNames);
}

return $this->normalizedIgnoredNames;
}

/**
* @param \PHP_CodeSniffer_File $phpcsFile
* @param integer $openTagPointer
Expand All @@ -58,10 +76,13 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
if (isset($useStatements[$normalizedName]) && $referencedName->hasSameUseStatementType($useStatements[$normalizedName])) {
$useStatement = $useStatements[$normalizedName];
if (
!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Exception')
&& $useStatement->getFullyQualifiedTypeName() !== 'Throwable'
&& (!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Error') || NamespaceHelper::hasNamespace($useStatement->getFullyQualifiedTypeName()))
&& !in_array($useStatement->getFullyQualifiedTypeName(), $this->getSpecialExceptionNames(), true)
in_array($useStatement->getFullyQualifiedTypeName(), $this->getIgnoredNames(), true)
|| (
!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Exception')
&& $useStatement->getFullyQualifiedTypeName() !== 'Throwable'
&& (!StringHelper::endsWith($useStatement->getFullyQualifiedTypeName(), 'Error') || NamespaceHelper::hasNamespace($useStatement->getFullyQualifiedTypeName()))
&& !in_array($useStatement->getFullyQualifiedTypeName(), $this->getSpecialExceptionNames(), true)
)
) {
continue;
}
Expand All @@ -72,10 +93,14 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
$canonicalName = sprintf('%s%s%s', $fileNamespace, NamespaceHelper::NAMESPACE_SEPARATOR, $name);
}
if (
!StringHelper::endsWith($name, 'Exception')
&& $name !== 'Throwable'
&& (!StringHelper::endsWith($canonicalName, 'Error') || NamespaceHelper::hasNamespace($canonicalName))
&& !in_array($canonicalName, $this->getSpecialExceptionNames(), true)) {
in_array($canonicalName, $this->getIgnoredNames(), true)
|| (
!StringHelper::endsWith($name, 'Exception')
&& $name !== 'Throwable'
&& (!StringHelper::endsWith($canonicalName, 'Error') || NamespaceHelper::hasNamespace($canonicalName))
&& !in_array($canonicalName, $this->getSpecialExceptionNames(), true)
)
) {
continue;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class ReferenceUsedNamesOnlySniff implements \PHP_CodeSniffer_Sniff
/** @var string[]|null */
private $normalizedSpecialExceptionNames;

/** @var string[] */
public $ignoredNames = [];

/** @var string[] */
private $normalizedIgnoredNames;

/** @var boolean */
public $allowPartialUses = true;

Expand Down Expand Up @@ -68,6 +74,18 @@ private function getSpecialExceptionNames()
return $this->normalizedSpecialExceptionNames;
}

/**
* @return string[]
*/
private function getIgnoredNames()
{
if ($this->normalizedIgnoredNames === null) {
$this->normalizedIgnoredNames = SniffSettingsHelper::normalizeArray($this->ignoredNames);
}

return $this->normalizedIgnoredNames;
}

/**
* @return string[]
*/
Expand Down Expand Up @@ -108,22 +126,28 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $openTagPointer)
foreach ($referencedNames as $referencedName) {
$name = $referencedName->getNameAsReferencedInFile();
$pointer = $referencedName->getPointer();
$canonicalName = NamespaceHelper::normalizeToCanonicalName($name);
if (NamespaceHelper::isFullyQualifiedName($name)) {
$isExceptionByName = StringHelper::endsWith($name, 'Exception')
|| $name === '\Throwable'
|| (StringHelper::endsWith($name, 'Error') && !NamespaceHelper::hasNamespace($name))
|| in_array($canonicalName, $this->getSpecialExceptionNames(), true);
$inIgnoredNames = in_array($canonicalName, $this->getIgnoredNames(), true);
if (
$this->isClassRequiredToBeUsed($name) &&
(
!(
StringHelper::endsWith($name, 'Exception')
|| $name === '\Throwable'
|| (StringHelper::endsWith($name, 'Error') && !NamespaceHelper::hasNamespace($name))
|| in_array(NamespaceHelper::normalizeToCanonicalName($name), $this->getSpecialExceptionNames(), true)
) || !$this->allowFullyQualifiedExceptions
) && $this->isClassRequiredToBeUsed($name)
!$this->allowFullyQualifiedExceptions
|| !$isExceptionByName
|| $inIgnoredNames
)
) {
$previousKeywordPointer = TokenHelper::findPreviousExcluding($phpcsFile, array_merge(
TokenHelper::$nameTokenCodes,
[T_WHITESPACE, T_COMMA]
), $pointer - 1);
if (!in_array($tokens[$previousKeywordPointer]['code'], $this->getFullyQualifiedKeywords(), true)) {
if (
!in_array($tokens[$previousKeywordPointer]['code'], $this->getFullyQualifiedKeywords(), true)
) {
if (
!NamespaceHelper::hasNamespace($name)
&& NamespaceHelper::findCurrentNamespaceName($phpcsFile, $pointer) === null
Expand Down
37 changes: 37 additions & 0 deletions tests/Sniffs/Namespaces/FullyQualifiedExceptionsSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,41 @@ public function testFullyQualifiedExceptionInCatch()
$this->assertNoSniffError($this->getFileReport(), 28);
}

public function testClassSuffixedErrorOrExceptionIsNotAnExceptionButReported()
{
$report = $this->checkFile(__DIR__ . '/data/ignoredNames.php');
$this->assertSniffError($report, 3, FullyQualifiedExceptionsSniff::CODE_NON_FULLY_QUALIFIED_EXCEPTION);
$this->assertSniffError($report, 7, FullyQualifiedExceptionsSniff::CODE_NON_FULLY_QUALIFIED_EXCEPTION);
}

public function testIgnoredNames()
{
$report = $this->checkFile(__DIR__ . '/data/ignoredNames.php', [
'ignoredNames' => [
'LibXMLError',
'LibXMLException',
],
]);
$this->assertNoSniffErrorInFile($report);
}

public function testClassSuffixedErrorOrExceptionIsNotAnExceptionButReportedInNamespace()
{
$report = $this->checkFile(__DIR__ . '/data/ignoredNamesInNamespace.php');
$this->assertNoSniffError($report, 5); // *Error names are reported only with a root namespace
$this->assertSniffError($report, 9, FullyQualifiedExceptionsSniff::CODE_NON_FULLY_QUALIFIED_EXCEPTION);
$this->assertNoSniffError($report, 13); // look like Exception but isn't - handled by ReferenceUsedNamesOnlySniff
$this->assertNoSniffError($report, 17); // dtto
}

public function testIgnoredNamesInNamespace()
{
$report = $this->checkFile(__DIR__ . '/data/ignoredNamesInNamespace.php', [
'ignoredNames' => [
'IgnoredNames\\LibXMLException',
],
]);
$this->assertNoSniffErrorInFile($report);
}

}
Loading

0 comments on commit 53c24d3

Please sign in to comment.