Skip to content

Commit

Permalink
PHP 8.1: Added support for "readonly" keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Nov 20, 2021
1 parent 5fb9b64 commit 99f72fa
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="NamedFunctionCallArgumentsTest.php" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.inc" role="test" />
<file baseinstalldir="" name="NullsafeObjectOperatorTest.php" role="test" />
<file baseinstalldir="" name="ReadonlyTest.inc" role="test" />
<file baseinstalldir="" name="ReadonlyTest.php" role="test" />
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.inc" role="test" />
<file baseinstalldir="" name="ScopeSettingWithNamespaceOperatorTest.php" role="test" />
<file baseinstalldir="" name="ShortArrayTest.inc" role="test" />
Expand Down Expand Up @@ -2092,6 +2094,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ReadonlyTest.php" name="tests/Core/Tokenizer/ReadonlyTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ReadonlyTest.inc" name="tests/Core/Tokenizer/ReadonlyTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
Expand Down Expand Up @@ -2182,6 +2186,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc" name="tests/Core/Tokenizer/NamedFunctionCallArgumentsTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.php" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/NullsafeObjectOperatorTest.inc" name="tests/Core/Tokenizer/NullsafeObjectOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ReadonlyTest.php" name="tests/Core/Tokenizer/ReadonlyTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ReadonlyTest.inc" name="tests/Core/Tokenizer/ReadonlyTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.php" />
<install as="CodeSniffer/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" name="tests/Core/Tokenizer/ScopeSettingWithNamespaceOperatorTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/ShortArrayTest.php" name="tests/Core/Tokenizer/ShortArrayTest.php" />
Expand Down
66 changes: 66 additions & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ class PHP extends Tokenizer
T_PRIVATE => 7,
T_PUBLIC => 6,
T_PROTECTED => 9,
T_READONLY => 8,
T_REQUIRE => 7,
T_REQUIRE_ONCE => 12,
T_RETURN => 6,
Expand Down Expand Up @@ -2812,6 +2813,71 @@ protected function processAdditional()
$this->tokens[$x]['code'] = T_STRING;
$this->tokens[$x]['type'] = 'T_STRING';
}
} else if (($this->tokens[$i]['code'] === T_STRING && strtolower($this->tokens[$i]['content']) === 'readonly')
|| $this->tokens[$i]['code'] === T_READONLY
) {
/*
"readonly" keyword support
PHP < 8.1: Converts T_STRING to T_READONLY
PHP >= 8.1: Converts some T_READONLY to T_STRING because token_get_all() without the TOKEN_PARSE flag cannot distinguish between them in some situations
*/

$allowedAfter = [
T_STRING => T_STRING,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_NAME_FULLY_QUALIFIED => T_NAME_FULLY_QUALIFIED,
T_NAME_RELATIVE => T_NAME_RELATIVE,
T_NAME_QUALIFIED => T_NAME_QUALIFIED,
T_TYPE_UNION => T_TYPE_UNION,
T_ARRAY => T_ARRAY,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_NULL => T_FALSE,
T_NULLABLE => T_NULLABLE,
T_STATIC => T_STATIC,
T_PUBLIC => T_PUBLIC,
T_PROTECTED => T_PROTECTED,
T_PRIVATE => T_PRIVATE,
T_VAR => T_VAR,
];

$shouldBeReadonly = true;

for ($x = ($i + 1); $x < $numTokens; $x++) {
if (isset(Util\Tokens::$emptyTokens[$this->tokens[$x]['code']]) === true) {
continue;
}

if ($this->tokens[$x]['code'] === T_VARIABLE) {
break;
}

if (isset($allowedAfter[$this->tokens[$x]['code']]) === false) {
$shouldBeReadonly = false;
break;
}
}

if ($this->tokens[$i]['code'] === T_STRING && $shouldBeReadonly === true) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$i]['line'];
echo "\t* token $i on line $line changed from T_STRING to T_READONLY".PHP_EOL;
}

$this->tokens[$i]['code'] = T_READONLY;
$this->tokens[$i]['type'] = 'T_READONLY';
} else if ($this->tokens[$i]['code'] === T_READONLY && $shouldBeReadonly === false) {
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$i]['line'];
echo "\t* token $i on line $line changed from T_READONLY to T_STRING".PHP_EOL;
}

$this->tokens[$i]['code'] = T_STRING;
$this->tokens[$i]['type'] = 'T_STRING';
}

continue;
}//end if

if (($this->tokens[$i]['code'] !== T_CASE
Expand Down
4 changes: 4 additions & 0 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@
define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', 'PHPCS_T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG');
}

if (defined('T_READONLY') === false) {
define('T_READONLY', 'PHPCS_T_READONLY');
}

// Tokens used for parsing doc blocks.
define('T_DOC_COMMENT_STAR', 'PHPCS_T_DOC_COMMENT_STAR');
define('T_DOC_COMMENT_WHITESPACE', 'PHPCS_T_DOC_COMMENT_WHITESPACE');
Expand Down
92 changes: 92 additions & 0 deletions tests/Core/Tokenizer/ReadonlyTest.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

class Foo
{
/* testReadonlyProperty */
readonly int $readonlyProperty;
/* testVarReadonlyProperty */
var readonly int $varReadonlyProperty;
/* testReadonlyVarProperty */
readonly var int $testReadonlyVarProperty;
/* testStaticReadonlyProperty */
static readonly int $staticReadonlyProperty;
/* testReadonlyStaticProperty */
readonly static int $readonlyStaticProperty;
/* testReadonlyPropertyWithoutType */
readonly $propertyWithoutType;
/* testPublicReadonlyProperty */
public readonly int $publicReadonlyProperty;
/* testProtectedReadonlyProperty */
protected readonly int $protectedReadonlyProperty;
/* testPrivateReadonlyProperty */
private readonly int $privateReadonlyProperty;
/* testPublicReadonlyPropertyWithReadonlyFirst */
readonly public int $publicReadonlyProperty;
/* testProtectedReadonlyPropertyWithReadonlyFirst */
readonly protected int $protectedReadonlyProperty;
/* testPrivateReadonlyPropertyWithReadonlyFirst */
readonly private int $privateReadonlyProperty;
/* testReadonlyWithCommentsInDeclaration */
private /* Comment */ readonly /* Comment */ int /* Comment */ $readonlyPropertyWithCommentsInDeclaration;
/* testReadonlyWithNullableProperty */
private readonly ?int $nullableProperty;
/* testReadonlyNullablePropertyWithUnionTypeHintAndNullFirst */
private readonly null|int $nullablePropertyWithUnionTypeHintAndNullFirst;
/* testReadonlyNullablePropertyWithUnionTypeHintAndNullLast */
private readonly int|null $nullablePropertyWithUnionTypeHintAndNullLast;
/* testReadonlyPropertyWithArrayTypeHint */
private readonly array $arrayProperty;
/* testReadonlyPropertyWithSelfTypeHint */
private readonly self $selfProperty;
/* testReadonlyPropertyWithParentTypeHint */
private readonly parent $parentProperty;
/* testReadonlyPropertyWithFullyQualifiedTypeHint */
private readonly \stdClass $propertyWithFullyQualifiedTypeHint;

/* testReadonlyIsCaseInsensitive */
public ReAdOnLy string $caseInsensitiveProperty;

/* testReadonlyConstructorPropertyPromotion */
public function __construct(private readonly bool $constructorPropertyPromotion)
{
}
}

$anonymousClass = new class () {
/* testReadonlyPropertyInAnonymousClass */
public readonly int $property;
};

class ClassName {
/* testReadonlyUsedAsClassConstantName */
const READONLY = 'readonly';

/* testReadonlyUsedAsMethodName */
public function readonly() {
// Do something.

/* testReadonlyUsedAsPropertyName */
$this->readonly = 'foo';

/* testReadonlyPropertyInTernaryOperator */
$isReadonly = $this->readonly ? true : false;
}
}

/* testReadonlyUsedAsFunctionName */
function readonly()
{
}

/* testReadonlyUsedAsNamespaceName */
namespace Readonly;
/* testReadonlyUsedAsPartOfNamespaceName */
namespace My\Readonly\Collection;
/* testReadonlyAsFunctionCall */
$var = readonly($a, $b);
/* testClassConstantFetchWithReadonlyAsConstantName */
echo ClassName::READONLY;

/* testParseErrorLiveCoding */
// This must be the last test in the file.
readonly
Loading

0 comments on commit 99f72fa

Please sign in to comment.