Skip to content

Commit

Permalink
Merge pull request #396 from jrfnl/feature/anonymous-class-vs-interfa…
Browse files Browse the repository at this point in the history
…ce-sniffs

Add anonymous class support in the InternalInterfaces and NewInterfaces sniffs.
  • Loading branch information
wimg committed Apr 26, 2017
2 parents f197b0f + c337455 commit 583757c
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 38 deletions.
10 changes: 7 additions & 3 deletions Sniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,10 @@ public function arrayKeysToLowercase($array)
* Returns FALSE on error or if there are no implemented interface names.
*
* {@internal Duplicate of same method as introduced in PHPCS 2.7.
* This method also includes an improvement we use which was only introduced
* in PHPCS 2.8.0, so only defer to upstream for higher versions.
* Once the minimum supported PHPCS version for this sniff library goes beyond
* that, this method can be removed and call to it replaced with
* that, this method can be removed and calls to it replaced with
* `$phpcsFile->findImplementedInterfaceNames($stackPtr)` calls.}}
*
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
Expand All @@ -312,7 +314,7 @@ public function arrayKeysToLowercase($array)
*/
public function findImplementedInterfaceNames(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
{
if (method_exists($phpcsFile, 'findImplementedInterfaceNames')) {
if (version_compare(PHP_CodeSniffer::VERSION, '2.7.1', '>') === true) {
return $phpcsFile->findImplementedInterfaceNames($stackPtr);
}

Expand All @@ -323,7 +325,9 @@ public function findImplementedInterfaceNames(PHP_CodeSniffer_File $phpcsFile, $
return false;
}

if ($tokens[$stackPtr]['code'] !== T_CLASS) {
if ($tokens[$stackPtr]['code'] !== T_CLASS
&& $tokens[$stackPtr]['type'] !== 'T_ANON_CLASS'
) {
return false;
}

Expand Down
8 changes: 7 additions & 1 deletion Sniffs/PHP/InternalInterfacesSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ public function register()
// Handle case-insensitivity of interface names.
$this->internalInterfaces = $this->arrayKeysToLowercase($this->internalInterfaces);

return array(T_CLASS);
$targets = array(T_CLASS);

if (defined('T_ANON_CLASS')) {
$targets[] = constant('T_ANON_CLASS');
}

return $targets;

}//end register()

Expand Down
8 changes: 7 additions & 1 deletion Sniffs/PHP/NewInterfacesSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ public function register()
$this->newInterfaces = $this->arrayKeysToLowercase($this->newInterfaces);
$this->unsupportedMethods = $this->arrayKeysToLowercase($this->unsupportedMethods);

return array(T_CLASS);
$targets = array(T_CLASS);

if (defined('T_ANON_CLASS')) {
$targets[] = constant('T_ANON_CLASS');
}

return $targets;

}//end register()

Expand Down
62 changes: 36 additions & 26 deletions Tests/Sniffs/PHP/InternalInterfacesSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ class InternalInterfacesSniffTest extends BaseSniffTest
*/
protected $_sniffFile;

/**
* Interface error messages.
*
* @var array
*/
protected $messages = array(
'Traversable' => 'The interface Traversable shouldn\'t be implemented directly, implement the Iterator or IteratorAggregate interface instead.',
'DateTimeInterface' => 'The interface DateTimeInterface is intended for type hints only and is not implementable.',
'Throwable' => 'The interface Throwable cannot be implemented directly, extend the Exception class instead.',
);

/**
* Set up the test file for this unit test.
*
Expand All @@ -43,44 +54,43 @@ protected function setUp()
}

/**
* Test Traversable interface
* Test InternalInterfaces
*
* @return void
*/
public function testTraversable()
{
$this->assertError($this->_sniffFile, 3, 'The interface Traversable shouldn\'t be implemented directly, implement the Iterator or IteratorAggregate interface instead.');
}

/**
* Test DateTimeInterface interface
* @dataProvider dataInternalInterfaces
*
* @return void
*/
public function testDateTimeInterface()
{
$this->assertError($this->_sniffFile, 4, 'The interface DateTimeInterface is intended for type hints only and is not implementable.');
}

/**
* Test Throwable interface
* @param string $interface Interface name.
* @param array $line The line number in the test file.
*
* @return void
*/
public function testThrowable()
public function testInternalInterfaces($type, $line)
{
$this->assertError($this->_sniffFile, 5, 'The interface Throwable cannot be implemented directly, extend the Exception class instead.');
$this->assertError($this->_sniffFile, $line, $this->messages[$type]);
}

/**
* Test multiple interfaces
* Data provider.
*
* @return void
* @see testInternalInterfaces()
*
* @return array
*/
public function testMultipleInterfaces()
public function dataInternalInterfaces()
{
$this->assertError($this->_sniffFile, 7, 'The interface Traversable shouldn\'t be implemented directly, implement the Iterator or IteratorAggregate interface instead.');
$this->assertError($this->_sniffFile, 7, 'The interface Throwable cannot be implemented directly, extend the Exception class instead.');
return array(
array('Traversable', 3),
array('DateTimeInterface', 4),
array('Throwable', 5),
array('Traversable', 7),
array('Throwable', 7),

// Anonymous classes.
array('Traversable', 17),
array('DateTimeInterface', 18),
array('Throwable', 19),
array('Traversable', 20),
array('Throwable', 20),
);
}

/**
Expand Down
32 changes: 27 additions & 5 deletions Tests/Sniffs/PHP/NewInterfacesSniffTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ public function dataNewInterface()
array('Countable', '5.0', array(3, 17), '5.1'),
array('OuterIterator', '5.0', array(4), '5.1'),
array('RecursiveIterator', '5.0', array(5), '5.1'),
array('SeekableIterator', '5.0', array(6, 17), '5.1'),
array('Serializable', '5.0', array(7), '5.1'),
array('SeekableIterator', '5.0', array(6, 17, 28), '5.1'),
array('Serializable', '5.0', array(7, 29), '5.1'),
array('SplObserver', '5.0', array(11), '5.1'),
array('SplSubject', '5.0', array(12, 17), '5.1'),
array('JsonSerializable', '5.3', array(13), '5.4'),
Expand All @@ -74,15 +74,37 @@ public function dataNewInterface()
/**
* Test unsupported methods
*
* @dataProvider dataUnsupportedMethods
*
* @param array $line The line number.
* @param string $methodName The name of the unsupported method which should be detected.
*
* @return void
*/
public function testUnsupportedMethods()
public function testUnsupportedMethods($line, $methodName)
{
$file = $this->sniffFile(self::TEST_FILE, '5.1'); // Version in which the Serializable interface was introduced.
$this->assertError($file, 8, 'Classes that implement interface Serializable do not support the method __sleep(). See http://php.net/serializable');
$this->assertError($file, 9, 'Classes that implement interface Serializable do not support the method __wakeup(). See http://php.net/serializable');
$this->assertError($file, $line, "Classes that implement interface Serializable do not support the method {$methodName}(). See http://php.net/serializable");
}

/**
* Data provider.
*
* @see testUnsupportedMethods()
*
* @return array
*/
public function dataUnsupportedMethods()
{
return array(
array(8, '__sleep'),
array(9, '__wakeup'),
array(30, '__sleep'),
array(31, '__wakeup'),
);
}


/**
* Test interfaces in different cases.
*
Expand Down
6 changes: 6 additions & 0 deletions Tests/sniff-examples/internal_interfaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ class MyLowercase implements datetimeinterface {} // Test case-insensitivity.
// These shouldn't throw errors.
class MyTraversable implements TraversableSomething {}
class MyTraversable implements myNameSpace\Traversable {}

// Internal interfaces with anonymous classes.
$a = new class implements Traversable {}
$b = new class implements DateTimeInterface {}
$c = new class implements Throwable {}
$d = new class implements SomeInterface, Throwable, AnotherInterface, Traversable {} // Test multiple interfaces.
11 changes: 9 additions & 2 deletions Tests/sniff-examples/new_interfaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class MyOuterIterator implements OuterIterator {}
class MyRecursiveIterator implements RecursiveIterator {}
class MySeekableIterator implements SeekableIterator {}
class MySerializable implements Serializable {
public function __sleep() {}
public function __wakeup() {}
public function __sleep() {}
public function __wakeup() {}
}
class MySplObserver implements SplObserver {}
class MySplSubject implements SplSubject {}
Expand All @@ -23,3 +23,10 @@ class MyLowercase implements countable {}
// These shouldn't throw errors.
class MyJsonSerializable implements JsonSerializableSomething {}
class MyJsonSerializable implements myNameSpace\JsonSerializable {}

// Test anonymous class support.
$a = new class implements SeekableIterator {}
$b = new class implements Serializable {
public function __sleep() {}
public function __wakeup() {}
}

0 comments on commit 583757c

Please sign in to comment.