diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 2c460889fd..89749c6ecb 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -608,8 +608,11 @@ protected function tokenize($string) ) { $preserveKeyword = false; - // `new class` should be preserved - if ($token[0] === T_CLASS && $finalTokens[$lastNotEmptyToken]['code'] === T_NEW) { + // `new class`, and `new static` should be preserved. + if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW + && ($token[0] === T_CLASS + || $token[0] === T_STATIC) + ) { $preserveKeyword = true; } @@ -1968,16 +1971,24 @@ function return types. We want to keep the parenthesis map clean, && $token[0] === T_STRING && isset($this->tstringContexts[$finalTokens[$lastNotEmptyToken]['code']]) === true ) { - // Special case for syntax like: return new self - // where self should not be a string. + // Special case for syntax like: return new self/new parent + // where self/parent should not be a string. + $tokenContentLower = strtolower($token[1]); if ($finalTokens[$lastNotEmptyToken]['code'] === T_NEW - && strtolower($token[1]) === 'self' + && ($tokenContentLower === 'self' || $tokenContentLower === 'parent') ) { $finalTokens[$newStackPtr] = [ 'content' => $token[1], - 'code' => T_SELF, - 'type' => 'T_SELF', ]; + if ($tokenContentLower === 'self') { + $finalTokens[$newStackPtr]['code'] = T_SELF; + $finalTokens[$newStackPtr]['type'] = 'T_SELF'; + } + + if ($tokenContentLower === 'parent') { + $finalTokens[$newStackPtr]['code'] = T_PARENT; + $finalTokens[$newStackPtr]['type'] = 'T_PARENT'; + } } else { $finalTokens[$newStackPtr] = [ 'content' => $token[1], diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc index eb1ca72058..0b073fcadf 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.inc @@ -1,210 +1,214 @@ - 'a', - 2 => 'b', - /* testMatchDefaultIsKeyword */ default => 'default', -}; - -$closure = /* testFnIsKeyword */ fn () => 'string'; - -function () { - /* testYieldIsKeyword */ yield $f; - /* testYieldFromIsKeyword */ yield from someFunction(); -}; - -/* testDeclareIsKeyword */ declare(ticks=1): -/* testEndDeclareIsKeyword */ enddeclare; - -if (true /* testAndIsKeyword */ and false /* testOrIsKeyword */ or null /* testXorIsKeyword */ xor 0) { - -} - -$anonymousClass = new /* testAnonymousClassIsKeyword */ class {}; -$anonymousClass2 = new class /* testExtendsInAnonymousClassIsKeyword */ extends SomeParent {}; -$anonymousClass3 = new class /* testImplementsInAnonymousClassIsKeyword */ implements SomeInterface {}; - -class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception -{} + 'a', + 2 => 'b', + /* testMatchDefaultIsKeyword */ default => 'default', +}; + +$closure = /* testFnIsKeyword */ fn () => 'string'; + +function () { + /* testYieldIsKeyword */ yield $f; + /* testYieldFromIsKeyword */ yield from someFunction(); +}; + +/* testDeclareIsKeyword */ declare(ticks=1): +/* testEndDeclareIsKeyword */ enddeclare; + +if (true /* testAndIsKeyword */ and false /* testOrIsKeyword */ or null /* testXorIsKeyword */ xor 0) { + +} + +$anonymousClass = new /* testAnonymousClassIsKeyword */ class {}; +$anonymousClass2 = new class /* testExtendsInAnonymousClassIsKeyword */ extends SomeParent {}; +$anonymousClass3 = new class /* testImplementsInAnonymousClassIsKeyword */ implements SomeInterface {}; + +$instantiated1 = new /* testClassInstantiationParentIsKeyword */ parent(); +$instantiated2 = new /* testClassInstantiationSelfIsKeyword */ SELF; +$instantiated3 = new /* testClassInstantiationStaticIsKeyword */ static($param); + +class Foo extends /* testNamespaceInNameIsKeyword */ namespace\Exception +{} diff --git a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php index 72aeac6859..7420fd38bf 100644 --- a/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php +++ b/tests/Core/Tokenizer/ContextSensitiveKeywordsTest.php @@ -462,6 +462,18 @@ public function dataKeywords() '/* testImplementsInAnonymousClassIsKeyword */', 'T_IMPLEMENTS', ], + [ + '/* testClassInstantiationParentIsKeyword */', + 'T_PARENT', + ], + [ + '/* testClassInstantiationSelfIsKeyword */', + 'T_SELF', + ], + [ + '/* testClassInstantiationStaticIsKeyword */', + 'T_STATIC', + ], [ '/* testNamespaceInNameIsKeyword */', 'T_NAMESPACE',