From f406777a6c7ed0703edc2d6e5478ca2bd6b5f6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20M=C3=B6ller?= Date: Sat, 16 Sep 2017 19:14:55 +0200 Subject: [PATCH 1/3] Enhancement: Assert that method names using reserved keywords are ignored --- test/ClassFileLocatorTest.php | 26 +++++++++++++++++++ .../WithReturnTypeDeclaration.php | 26 +++++++++++++++++++ .../WithoutReturnTypeDeclaration.php | 26 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 test/TestAsset/WithMethodsNamedAfterKeywords/WithReturnTypeDeclaration.php create mode 100644 test/TestAsset/WithMethodsNamedAfterKeywords/WithoutReturnTypeDeclaration.php diff --git a/test/ClassFileLocatorTest.php b/test/ClassFileLocatorTest.php index 7347752..a710c91 100644 --- a/test/ClassFileLocatorTest.php +++ b/test/ClassFileLocatorTest.php @@ -177,4 +177,30 @@ public function testIgnoresAnonymousClasses() $this->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..d53c83b --- /dev/null +++ b/test/TestAsset/WithMethodsNamedAfterKeywords/WithReturnTypeDeclaration.php @@ -0,0 +1,26 @@ + Date: Wed, 25 Apr 2018 11:03:04 -0500 Subject: [PATCH 2/3] Update license docblocks and RTH styling --- test/ClassFileLocatorTest.php | 13 +++---------- .../WithReturnTypeDeclaration.php | 13 +++++-------- .../WithoutReturnTypeDeclaration.php | 7 ++----- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/test/ClassFileLocatorTest.php b/test/ClassFileLocatorTest.php index a710c91..d4f4b4d 100644 --- a/test/ClassFileLocatorTest.php +++ b/test/ClassFileLocatorTest.php @@ -1,10 +1,8 @@ Date: Wed, 25 Apr 2018 11:25:50 -0500 Subject: [PATCH 3/3] Provides functionality to ignore methods named after keywords Patch #41 originally was providing a unit test demonstrating a problem in PHP 7.1 and up: the tokenizer is classifying method names such as `class`, `interface`, and `trait` as their associated tokens, instead of as literal strings. The changes in this patch add some logic to test for `T_FUNCTION` token and set a flag indicating we are in a function declaration. Once an opening paren (`(`) is discovered, the flag is reset. This prevents mis-identifying such function names as tokens. --- src/ClassFileLocator.php | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) 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];