Skip to content

Commit dcc8223

Browse files
committed
File: add new File::findExtendedInterfaceNames() utility method
As I've just looked at the `findExtendedClassName()` method again for PR 2127, I remembered that SenseException reported another issue with it a while back via Twitter: https://twitter.com/SenseException/status/997988691468484609. Summary: interfaces can extend more than one interface (in contrast to classes) and the `findExtendedClassName()` method does not allow for that. See: http://php.net/manual/en/language.oop5.interfaces.php#example-208 To allow for this from within the existing `findExtendedClassName()` method would change the function signature from `@return string|false` to `@return string|array|false`, thus breaking the API. So, instead, I've elected to add a new `File::findExtendedInterfaceNames()` method and to recommend using that method for interfaces in the documentation of the `findExtendedClassName()` method . As the logic needed for these two methods, as well as for the `findImplementedInterfaces()` method, is basically the same, I've extracted the logic out to a private `File::examineObjectDeclarationSignature()` method which is now called by all three functions to DRY out the code. Includes dedicated unit tests for the new method.
1 parent 4d88439 commit dcc8223

File tree

5 files changed

+271
-53
lines changed

5 files changed

+271
-53
lines changed

package.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
8080
<file baseinstalldir="" name="FindEndOfStatementTest.php" role="test" />
8181
<file baseinstalldir="" name="FindExtendedClassNameTest.inc" role="test" />
8282
<file baseinstalldir="" name="FindExtendedClassNameTest.php" role="test" />
83+
<file baseinstalldir="" name="FindExtendedInterfaceNamesTest.inc" role="test" />
84+
<file baseinstalldir="" name="FindExtendedInterfaceNamesTest.php" role="test" />
8385
<file baseinstalldir="" name="FindImplementedInterfaceNamesTest.inc" role="test" />
8486
<file baseinstalldir="" name="FindImplementedInterfaceNamesTest.php" role="test" />
8587
<file baseinstalldir="" name="GetMemberPropertiesTest.inc" role="test" />

src/Files/File.php

Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2355,109 +2355,153 @@ public function getCondition($stackPtr, $type)
23552355

23562356
/**
23572357
* Returns the name of the class that the specified class extends.
2358-
* (works for classes, anonymous classes and interfaces)
2358+
*
2359+
* Works for classes, anonymous classes and interfaces, though it is
2360+
* strongly recommended to use the findExtendedInterfaceNames() method
2361+
* to examine interfaces as they can extend multiple parent interfaces.
23592362
*
23602363
* Returns FALSE on error or if there is no extended class name.
23612364
*
2362-
* @param int $stackPtr The stack position of the class.
2365+
* @param int $stackPtr The stack position of the class keyword.
23632366
*
23642367
* @return string|false
23652368
*/
23662369
public function findExtendedClassName($stackPtr)
23672370
{
2368-
// Check for the existence of the token.
2369-
if (isset($this->tokens[$stackPtr]) === false) {
2370-
return false;
2371-
}
2371+
$validStructures = [
2372+
T_CLASS => true,
2373+
T_ANON_CLASS => true,
2374+
T_INTERFACE => true,
2375+
];
23722376

2373-
if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2374-
&& $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2375-
&& $this->tokens[$stackPtr]['code'] !== T_INTERFACE
2376-
) {
2377-
return false;
2378-
}
2377+
$classes = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_EXTENDS);
23792378

2380-
if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
2379+
if (empty($classes) === true) {
23812380
return false;
23822381
}
23832382

2384-
$classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2385-
$extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
2386-
if (false === $extendsIndex) {
2387-
return false;
2388-
}
2383+
// Classes can only extend one parent class.
2384+
return $classes[0];
23892385

2390-
$find = [
2391-
T_NS_SEPARATOR,
2392-
T_STRING,
2393-
T_WHITESPACE,
2394-
];
2386+
}//end findExtendedClassName()
23952387

2396-
$end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
2397-
$name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
2398-
$name = trim($name);
23992388

2400-
if ($name === '') {
2389+
/**
2390+
* Returns the names of the interfaces that the specified interface extends.
2391+
*
2392+
* Returns FALSE on error or if there is no extended interface name.
2393+
*
2394+
* @param int $stackPtr The stack position of the interface keyword.
2395+
*
2396+
* @return array|false
2397+
*/
2398+
public function findExtendedInterfaceNames($stackPtr)
2399+
{
2400+
$validStructures = [T_INTERFACE => true];
2401+
2402+
$interfaces = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_EXTENDS);
2403+
2404+
if (empty($interfaces) === true) {
24012405
return false;
24022406
}
24032407

2404-
return $name;
2408+
return $interfaces;
24052409

2406-
}//end findExtendedClassName()
2410+
}//end findExtendedInterfaceNames()
24072411

24082412

24092413
/**
24102414
* Returns the names of the interfaces that the specified class implements.
24112415
*
24122416
* Returns FALSE on error or if there are no implemented interface names.
24132417
*
2414-
* @param int $stackPtr The stack position of the class.
2418+
* @param int $stackPtr The stack position of the class keyword.
24152419
*
24162420
* @return array|false
24172421
*/
24182422
public function findImplementedInterfaceNames($stackPtr)
2423+
{
2424+
$validStructures = [
2425+
T_CLASS => true,
2426+
T_ANON_CLASS => true,
2427+
];
2428+
2429+
$interfaces = $this->examineObjectDeclarationSignature($stackPtr, $validStructures, T_IMPLEMENTS);
2430+
2431+
if (empty($interfaces) === true) {
2432+
return false;
2433+
}
2434+
2435+
return $interfaces;
2436+
2437+
}//end findImplementedInterfaceNames()
2438+
2439+
2440+
/**
2441+
* Returns the names of the extended classes or interfaces or the implemented
2442+
* interfaces that the specific class/interface declaration extends/implements.
2443+
*
2444+
* Returns FALSE on error or if the object does not extend/implement another
2445+
* object.
2446+
*
2447+
* @param int $stackPtr The stack position of the class/interface keyword.
2448+
* @param array $OOTypes Array of accepted token types.
2449+
* Array format <token constant> => true.
2450+
* @param int $keyword The keyword to examine. Either `T_EXTENDS` or `T_IMPLEMENTS`.
2451+
*
2452+
* @return array|false
2453+
*/
2454+
private function examineObjectDeclarationSignature($stackPtr, $OOTypes, $keyword)
24192455
{
24202456
// Check for the existence of the token.
24212457
if (isset($this->tokens[$stackPtr]) === false) {
24222458
return false;
24232459
}
24242460

2425-
if ($this->tokens[$stackPtr]['code'] !== T_CLASS
2426-
&& $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
2427-
) {
2461+
if (isset($OOTypes[$this->tokens[$stackPtr]['code']]) === false) {
24282462
return false;
24292463
}
24302464

2431-
if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
2465+
if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
24322466
return false;
24332467
}
24342468

2435-
$classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
2436-
$implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
2437-
if ($implementsIndex === false) {
2469+
$openerIndex = $this->tokens[$stackPtr]['scope_opener'];
2470+
$keywordIndex = $this->findNext($keyword, ($stackPtr + 1), $openerIndex);
2471+
if ($keywordIndex === false) {
24382472
return false;
24392473
}
24402474

2441-
$find = [
2442-
T_NS_SEPARATOR,
2443-
T_STRING,
2444-
T_WHITESPACE,
2445-
T_COMMA,
2446-
];
2475+
$find = Util\Tokens::$emptyTokens;
2476+
$find[] = T_NS_SEPARATOR;
2477+
$find[] = T_STRING;
2478+
$find[] = T_COMMA;
24472479

2448-
$end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
2449-
$name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
2450-
$name = trim($name);
2480+
$end = $this->findNext($find, ($keywordIndex + 1), ($openerIndex + 1), true);
2481+
$names = [];
2482+
$name = '';
2483+
for ($i = ($keywordIndex + 1); $i < $end; $i++) {
2484+
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === true) {
2485+
continue;
2486+
}
24512487

2452-
if ($name === '') {
2453-
return false;
2454-
} else {
2455-
$names = explode(',', $name);
2456-
$names = array_map('trim', $names);
2457-
return $names;
2488+
if ($this->tokens[$i]['code'] === T_COMMA && $name !== '') {
2489+
$names[] = $name;
2490+
$name = '';
2491+
continue;
2492+
}
2493+
2494+
$name .= $this->tokens[$i]['content'];
24582495
}
24592496

2460-
}//end findImplementedInterfaceNames()
2497+
// Add the last name.
2498+
if ($name !== '') {
2499+
$names[] = $name;
2500+
}
2501+
2502+
return $names;
2503+
2504+
}//end examineObjectDeclarationSignature()
24612505

24622506

24632507
}//end class

tests/Core/AllTests.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require_once 'ErrorSuppressionTest.php';
1717
require_once 'File/FindEndOfStatementTest.php';
1818
require_once 'File/FindExtendedClassNameTest.php';
19+
require_once 'File/FindExtendedInterfaceNamesTest.php';
1920
require_once 'File/FindImplementedInterfaceNamesTest.php';
2021
require_once 'File/GetMemberPropertiesTest.php';
2122
require_once 'File/GetMethodParametersTest.php';
@@ -50,6 +51,7 @@ public static function suite()
5051
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\ErrorSuppressionTest');
5152
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindEndOfStatementTest');
5253
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindExtendedClassNameTest');
54+
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindExtendedInterfaceNamesTest');
5355
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\FindImplementedInterfaceNamesTest');
5456
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMemberPropertiesTest');
5557
$suite->addTestSuite('PHP_CodeSniffer\Tests\Core\File\GetMethodParametersTest');
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/* @codingStandardsIgnoreFile */
3+
4+
namespace PHP_CodeSniffer\Tests\Core\File;
5+
6+
interface testFEINInterface2 {}
7+
8+
/* testInterface */
9+
interface testFEINInterface {}
10+
11+
/* testExtendedInterface */
12+
interface testFEINExtendedInterface extends testFEINInterface {}
13+
14+
/* testMultiExtendedInterface */
15+
interface testFEINMultiExtendedInterface extends testFEINInterface, testFEINInterface2 {}
16+
17+
/* testNamespacedInterface */
18+
interface testFEINNamespacedInterface extends \PHP_CodeSniffer\Tests\Core\File\testFEINInterface {}
19+
20+
/* testMultiNamespacedInterface */
21+
interface testFEINMultiNamespacedInterface extends \PHP_CodeSniffer\Tests\Core\File\testFEINInterface, \PHP_CodeSniffer\Tests\Core\File\testFEINInterface2 {}
22+
23+
/* testMultiExtendedInterfaceWithComment */
24+
interface testFEINMultiExtendedInterface
25+
extends
26+
/* a comment */
27+
testFEINInterface,
28+
\PHP_CodeSniffer\Tests /* comment */ \Core \ File \testFEINInterface2,
29+
\testFEINInterface3 /* comment */
30+
{
31+
}

0 commit comments

Comments
 (0)