diff --git a/package.xml b/package.xml
index 381c82dfb0..3f09663042 100644
--- a/package.xml
+++ b/package.xml
@@ -80,6 +80,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
+
+
diff --git a/src/Files/File.php b/src/Files/File.php
index eb56ef64b4..a5fdc67e00 100644
--- a/src/Files/File.php
+++ b/src/Files/File.php
@@ -2355,55 +2355,59 @@ public function getCondition($stackPtr, $type)
/**
* Returns the name of the class that the specified class extends.
- * (works for classes, anonymous classes and interfaces)
+ *
+ * Works for classes, anonymous classes and interfaces, though it is
+ * strongly recommended to use the findExtendedInterfaceNames() method
+ * to examine interfaces as they can extend multiple parent interfaces.
*
* Returns FALSE on error or if there is no extended class name.
*
- * @param int $stackPtr The stack position of the class.
+ * @param int $stackPtr The stack position of the class keyword.
*
* @return string|false
*/
public function findExtendedClassName($stackPtr)
{
- // Check for the existence of the token.
- if (isset($this->tokens[$stackPtr]) === false) {
- return false;
- }
+ $validStructures = [
+ T_CLASS => true,
+ T_ANON_CLASS => true,
+ T_INTERFACE => true,
+ ];
- if ($this->tokens[$stackPtr]['code'] !== T_CLASS
- && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
- && $this->tokens[$stackPtr]['code'] !== T_INTERFACE
- ) {
- return false;
- }
+ $classes = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_EXTENDS);
- if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
+ if (empty($classes) === true) {
return false;
}
- $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
- $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
- if (false === $extendsIndex) {
- return false;
- }
+ // Classes can only extend one parent class.
+ return $classes[0];
- $find = [
- T_NS_SEPARATOR,
- T_STRING,
- T_WHITESPACE,
- ];
+ }//end findExtendedClassName()
- $end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
- $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
- $name = trim($name);
- if ($name === '') {
+ /**
+ * Returns the names of the interfaces that the specified interface extends.
+ *
+ * Returns FALSE on error or if there is no extended interface name.
+ *
+ * @param int $stackPtr The stack position of the interface keyword.
+ *
+ * @return array|false
+ */
+ public function findExtendedInterfaceNames($stackPtr)
+ {
+ $validStructures = [T_INTERFACE => true];
+
+ $interfaces = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_EXTENDS);
+
+ if (empty($interfaces) === true) {
return false;
}
- return $name;
+ return $interfaces;
- }//end findExtendedClassName()
+ }//end findExtendedInterfaceNames()
/**
@@ -2411,53 +2415,93 @@ public function findExtendedClassName($stackPtr)
*
* Returns FALSE on error or if there are no implemented interface names.
*
- * @param int $stackPtr The stack position of the class.
+ * @param int $stackPtr The stack position of the class keyword.
*
* @return array|false
*/
public function findImplementedInterfaceNames($stackPtr)
+ {
+ $validStructures = [
+ T_CLASS => true,
+ T_ANON_CLASS => true,
+ ];
+
+ $interfaces = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_IMPLEMENTS);
+
+ if (empty($interfaces) === true) {
+ return false;
+ }
+
+ return $interfaces;
+
+ }//end findImplementedInterfaceNames()
+
+
+ /**
+ * Returns the names of the extended classes or interfaces or the implemented
+ * interfaces that the specific class/interface declaration extends/implements.
+ *
+ * Returns FALSE on error or if the object does not extend/implement another
+ * object.
+ *
+ * @param int $stackPtr The stack position of the class/interface keyword.
+ * @param array $OOTypes Array of accepted token types.
+ * Array format => true.
+ * @param int $keyword The keyword to examine. Either `T_EXTENDS` or `T_IMPLEMENTS`.
+ *
+ * @return array|false
+ */
+ private function examineObjectDeclarationSignature($stackPtr, $OOTypes, $keyword)
{
// Check for the existence of the token.
if (isset($this->tokens[$stackPtr]) === false) {
return false;
}
- if ($this->tokens[$stackPtr]['code'] !== T_CLASS
- && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
- ) {
+ if (isset($OOTypes[$this->tokens[$stackPtr]['code']]) === false) {
return false;
}
- if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
+ if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
return false;
}
- $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
- $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
- if ($implementsIndex === false) {
+ $openerIndex = $this->tokens[$stackPtr]['scope_opener'];
+ $keywordIndex = $this->findNext($keyword, ($stackPtr + 1), $openerIndex);
+ if ($keywordIndex === false) {
return false;
}
- $find = [
- T_NS_SEPARATOR,
- T_STRING,
- T_WHITESPACE,
- T_COMMA,
- ];
+ $find = Util\Tokens::$emptyTokens;
+ $find[] = T_NS_SEPARATOR;
+ $find[] = T_STRING;
+ $find[] = T_COMMA;
- $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
- $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
- $name = trim($name);
+ $end = $this->findNext($find, ($keywordIndex + 1), ($openerIndex + 1), true);
+ $names = [];
+ $name = '';
+ for ($i = ($keywordIndex + 1); $i < $end; $i++) {
+ if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === true) {
+ continue;
+ }
- if ($name === '') {
- return false;
- } else {
- $names = explode(',', $name);
- $names = array_map('trim', $names);
- return $names;
+ if ($this->tokens[$i]['code'] === T_COMMA && $name !== '') {
+ $names[] = $name;
+ $name = '';
+ continue;
+ }
+
+ $name .= $this->tokens[$i]['content'];
}
- }//end findImplementedInterfaceNames()
+ // Add the last name.
+ if ($name !== '') {
+ $names[] = $name;
+ }
+
+ return $names;
+
+ }//end examineObjectDeclarationSignature()
}//end class
diff --git a/tests/Core/AllTests.php b/tests/Core/AllTests.php
index 36a98a9ba7..0fd0235467 100644
--- a/tests/Core/AllTests.php
+++ b/tests/Core/AllTests.php
@@ -16,6 +16,7 @@
require_once 'ErrorSuppressionTest.php';
require_once 'File/FindEndOfStatementTest.php';
require_once 'File/FindExtendedClassNameTest.php';
+require_once 'File/FindExtendedInterfaceNamesTest.php';
require_once 'File/FindImplementedInterfaceNamesTest.php';
require_once 'File/GetMemberPropertiesTest.php';
require_once 'File/GetMethodParametersTest.php';
@@ -50,6 +51,7 @@ public static function suite()
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\ErrorSuppressionTest');
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindEndOfStatementTest');
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindExtendedClassNameTest');
+ $suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindExtendedInterfaceNamesTest');
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindImplementedInterfaceNamesTest');
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMemberPropertiesTest');
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMethodParametersTest');
diff --git a/tests/Core/File/FindExtendedInterfaceNamesTest.inc b/tests/Core/File/FindExtendedInterfaceNamesTest.inc
new file mode 100644
index 0000000000..fa37dada49
--- /dev/null
+++ b/tests/Core/File/FindExtendedInterfaceNamesTest.inc
@@ -0,0 +1,31 @@
+
+ * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
+ * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
+ */
+
+namespace PHP_CodeSniffer\Tests\Core\File;
+
+use PHP_CodeSniffer\Config;
+use PHP_CodeSniffer\Ruleset;
+use PHP_CodeSniffer\Files\DummyFile;
+use PHPUnit\Framework\TestCase;
+
+class FindExtendedInterfaceNamesTest extends TestCase
+{
+
+ /**
+ * The \PHP_CodeSniffer\Files\File object containing parsed contents of the test case file.
+ *
+ * @var \PHP_CodeSniffer\Files\File
+ */
+ private $phpcsFile;
+
+
+ /**
+ * Initialize & tokenize \PHP_CodeSniffer\Files\File with code from the test case file.
+ *
+ * Methods used for these tests can be found in a test case file in the same
+ * directory and with the same name, using the .inc extension.
+ *
+ * @return void
+ */
+ public function setUp()
+ {
+ $config = new Config();
+ $config->standards = ['Generic'];
+
+ $ruleset = new Ruleset($config);
+
+ $pathToTestFile = dirname(__FILE__).'/'.basename(__FILE__, '.php').'.inc';
+ $this->phpcsFile = new DummyFile(file_get_contents($pathToTestFile), $ruleset, $config);
+ $this->phpcsFile->process();
+
+ }//end setUp()
+
+
+ /**
+ * Clean up after finished test.
+ *
+ * @return void
+ */
+ public function tearDown()
+ {
+ unset($this->phpcsFile);
+
+ }//end tearDown()
+
+
+ /**
+ * Test retrieving the names of the interfaces being extended by another interface.
+ *
+ * @param string $identifier Comment which preceeds the test case.
+ * @param bool $expected Expected function output.
+ *
+ * @dataProvider dataExtendedInterface
+ *
+ * @return void
+ */
+ public function testFindExtendedInterfaceNames($identifier, $expected)
+ {
+ $start = ($this->phpcsFile->numTokens - 1);
+ $delim = $this->phpcsFile->findPrevious(
+ T_COMMENT,
+ $start,
+ null,
+ false,
+ $identifier
+ );
+ $interface = $this->phpcsFile->findNext(T_INTERFACE, ($delim + 1));
+
+ $result = $this->phpcsFile->findExtendedInterfaceNames($interface);
+ $this->assertSame($expected, $result);
+
+ }//end testFindExtendedInterfaceNames()
+
+
+ /**
+ * Data provider for the FindExtendedInterfaceNames test.
+ *
+ * @see testFindExtendedInterfaceNames()
+ *
+ * @return array
+ */
+ public function dataExtendedInterface()
+ {
+ return [
+ [
+ '/* testInterface */',
+ false,
+ ],
+ [
+ '/* testExtendedInterface */',
+ ['testFEINInterface'],
+ ],
+ [
+ '/* testMultiExtendedInterface */',
+ [
+ 'testFEINInterface',
+ 'testFEINInterface2',
+ ],
+ ],
+ [
+ '/* testNamespacedInterface */',
+ ['\PHP_CodeSniffer\Tests\Core\File\testFEINInterface'],
+ ],
+ [
+ '/* testMultiNamespacedInterface */',
+ [
+ '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface',
+ '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface2',
+ ],
+ ],
+ [
+ '/* testMultiExtendedInterfaceWithComment */',
+ [
+ 'testFEINInterface',
+ '\PHP_CodeSniffer\Tests\Core\File\testFEINInterface2',
+ '\testFEINInterface3',
+ ],
+ ],
+ ];
+
+ }//end dataExtendedInterface()
+
+
+}//end class