diff --git a/src/ClassFileLocator.php b/src/ClassFileLocator.php index 767fee4..8ac5f32 100644 --- a/src/ClassFileLocator.php +++ b/src/ClassFileLocator.php @@ -80,13 +80,22 @@ public function accept() $contents = file_get_contents($file->getRealPath()); $tokens = token_get_all($contents); $count = count($tokens); + $inFunctionDeclaration = false; for ($i = 0; $i < $count; $i++) { $token = $tokens[$i]; + + // single character token found; skip if (! is_array($token)) { - // single character token found; skip + // If we were in a function declaration, and we encounter an + // opening paren, reset the $inFunctionDeclaration flag. + if ('(' === $token) { + $inFunctionDeclaration = false; + } + $i++; continue; } + switch ($token[0]) { case T_NAMESPACE: // Namespace found; grab it for later @@ -116,6 +125,9 @@ public function accept() $savedNamespace = $namespace; } break; + case T_FUNCTION: + $inFunctionDeclaration = true; + break; case T_TRAIT: case T_CLASS: // ignore T_CLASS after T_DOUBLE_COLON to allow PHP >=5.5 FQCN scalar resolution @@ -123,6 +135,13 @@ public function accept() break; } + // Ignore if we are within a function declaration; + // functions are allowed to be named after keywords + // such as class, interface, and trait. + if ($inFunctionDeclaration) { + break; + } + // ignore anonymous classes on PHP 7.1 and greater if ($i >= 2 && \is_array($tokens[$i - 1]) @@ -137,6 +156,13 @@ public function accept() case T_INTERFACE: // Abstract class, class, interface or trait found + // Ignore if we are within a function declaration; + // functions are allowed to be named after keywords + // such as class, interface, and trait. + if ($inFunctionDeclaration) { + break; + } + // Get the classname for ($i++; $i < $count; $i++) { $token = $tokens[$i]; diff --git a/test/ClassFileLocatorTest.php b/test/ClassFileLocatorTest.php index 7347752..d4f4b4d 100644 --- a/test/ClassFileLocatorTest.php +++ b/test/ClassFileLocatorTest.php @@ -1,10 +1,8 @@ assertEquals($expected, $classNames); } + + /** + * @requires PHP 7.1 + */ + public function testIgnoresMethodsNamedAfterKeywords() + { + $classFileLocator = new ClassFileLocator(__DIR__ . '/TestAsset/WithMethodsNamedAfterKeywords'); + + $classFiles = \iterator_to_array($classFileLocator); + + $this->assertCount(2, $classFiles); + + $classNames = \array_reduce($classFiles, function (array $classNames, PhpClassFile $classFile) { + return \array_merge( + $classNames, + $classFile->getClasses() + ); + }, []); + + $expected = [ + TestAsset\WithMethodsNamedAfterKeywords\WithoutReturnTypeDeclaration::class, + TestAsset\WithMethodsNamedAfterKeywords\WithReturnTypeDeclaration::class, + ]; + + $this->assertEquals($expected, $classNames, '', 0.0, 10, true); + } } diff --git a/test/TestAsset/WithMethodsNamedAfterKeywords/WithReturnTypeDeclaration.php b/test/TestAsset/WithMethodsNamedAfterKeywords/WithReturnTypeDeclaration.php new file mode 100644 index 0000000..a07589a --- /dev/null +++ b/test/TestAsset/WithMethodsNamedAfterKeywords/WithReturnTypeDeclaration.php @@ -0,0 +1,23 @@ +