Skip to content

Commit 3f194e7

Browse files
committed
PHP 8.2 | File::getClassProperties(): add support for readonly classes
PHP 8.2 introduces `readonly` classes. The `readonly` keyword can be combined with the `abstract` or `final` keyword. See: https://3v4l.org/VIXgD Includes adding a full set of tests for the `File::getClassProperties()` method, which was so far untested. Ref: * https://wiki.php.net/rfc/readonly_classes
1 parent d7864cb commit 3f194e7

File tree

4 files changed

+260
-0
lines changed

4 files changed

+260
-0
lines changed

package.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
100100
<file baseinstalldir="" name="FindImplementedInterfaceNamesTest.php" role="test" />
101101
<file baseinstalldir="" name="FindStartOfStatementTest.inc" role="test" />
102102
<file baseinstalldir="" name="FindStartOfStatementTest.php" role="test" />
103+
<file baseinstalldir="" name="GetClassPropertiesTest.inc" role="test" />
104+
<file baseinstalldir="" name="GetClassPropertiesTest.php" role="test" />
103105
<file baseinstalldir="" name="GetMemberPropertiesTest.inc" role="test" />
104106
<file baseinstalldir="" name="GetMemberPropertiesTest.php" role="test" />
105107
<file baseinstalldir="" name="GetMethodParametersTest.inc" role="test" />
@@ -2083,6 +2085,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
20832085
<install as="CodeSniffer/Core/File/FindImplementedInterfaceNamesTest.inc" name="tests/Core/File/FindImplementedInterfaceNamesTest.inc" />
20842086
<install as="CodeSniffer/Core/File/FindStartOfStatementTest.php" name="tests/Core/File/FindStartOfStatementTest.php" />
20852087
<install as="CodeSniffer/Core/File/FindStartOfStatementTest.inc" name="tests/Core/File/FindStartOfStatementTest.inc" />
2088+
<install as="CodeSniffer/Core/File/GetClassPropertiesTest.php" name="tests/Core/File/GetClassPropertiesTest.php" />
2089+
<install as="CodeSniffer/Core/File/GetClassPropertiesTest.inc" name="tests/Core/File/GetClassPropertiesTest.inc" />
20862090
<install as="CodeSniffer/Core/File/GetMemberPropertiesTest.php" name="tests/Core/File/GetMemberPropertiesTest.php" />
20872091
<install as="CodeSniffer/Core/File/GetMemberPropertiesTest.inc" name="tests/Core/File/GetMemberPropertiesTest.inc" />
20882092
<install as="CodeSniffer/Core/File/GetMethodParametersTest.php" name="tests/Core/File/GetMethodParametersTest.php" />
@@ -2187,6 +2191,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
21872191
<install as="CodeSniffer/Core/File/FindImplementedInterfaceNamesTest.inc" name="tests/Core/File/FindImplementedInterfaceNamesTest.inc" />
21882192
<install as="CodeSniffer/Core/File/FindStartOfStatementTest.php" name="tests/Core/File/FindStartOfStatementTest.php" />
21892193
<install as="CodeSniffer/Core/File/FindStartOfStatementTest.inc" name="tests/Core/File/FindStartOfStatementTest.inc" />
2194+
<install as="CodeSniffer/Core/File/GetClassPropertiesTest.php" name="tests/Core/File/GetClassPropertiesTest.php" />
2195+
<install as="CodeSniffer/Core/File/GetClassPropertiesTest.inc" name="tests/Core/File/GetClassPropertiesTest.inc" />
21902196
<install as="CodeSniffer/Core/File/GetMemberPropertiesTest.php" name="tests/Core/File/GetMemberPropertiesTest.php" />
21912197
<install as="CodeSniffer/Core/File/GetMemberPropertiesTest.inc" name="tests/Core/File/GetMemberPropertiesTest.inc" />
21922198
<install as="CodeSniffer/Core/File/GetMethodParametersTest.php" name="tests/Core/File/GetMethodParametersTest.php" />

src/Files/File.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,6 +1947,7 @@ public function getMemberProperties($stackPtr)
19471947
* array(
19481948
* 'is_abstract' => false, // true if the abstract keyword was found.
19491949
* 'is_final' => false, // true if the final keyword was found.
1950+
* 'is_readonly' => false, // true if the readonly keyword was found.
19501951
* );
19511952
* </code>
19521953
*
@@ -1966,13 +1967,15 @@ public function getClassProperties($stackPtr)
19661967
$valid = [
19671968
T_FINAL => T_FINAL,
19681969
T_ABSTRACT => T_ABSTRACT,
1970+
T_READONLY => T_READONLY,
19691971
T_WHITESPACE => T_WHITESPACE,
19701972
T_COMMENT => T_COMMENT,
19711973
T_DOC_COMMENT => T_DOC_COMMENT,
19721974
];
19731975

19741976
$isAbstract = false;
19751977
$isFinal = false;
1978+
$isReadonly = false;
19761979

19771980
for ($i = ($stackPtr - 1); $i > 0; $i--) {
19781981
if (isset($valid[$this->tokens[$i]['code']]) === false) {
@@ -1987,12 +1990,17 @@ public function getClassProperties($stackPtr)
19871990
case T_FINAL:
19881991
$isFinal = true;
19891992
break;
1993+
1994+
case T_READONLY:
1995+
$isReadonly = true;
1996+
break;
19901997
}
19911998
}//end for
19921999

19932000
return [
19942001
'is_abstract' => $isAbstract,
19952002
'is_final' => $isFinal,
2003+
'is_readonly' => $isReadonly,
19962004
];
19972005

19982006
}//end getClassProperties()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/* testNotAClass */
4+
interface NotAClass {}
5+
6+
/* testAnonClass */
7+
$anon = new class() {};
8+
9+
/* testEnum */
10+
enum NotAClassEither {}
11+
12+
/* testClassWithoutProperties */
13+
class ClassWithoutProperties {}
14+
15+
/* testAbstractClass */
16+
abstract class AbstractClass {}
17+
18+
/* testFinalClass */
19+
final class FinalClass {}
20+
21+
/* testReadonlyClass */
22+
readonly class ReadOnlyClass {}
23+
24+
/* testFinalReadonlyClass */
25+
final readonly class FinalReadOnlyClass extends Foo {}
26+
27+
/* testReadonlyFinalClass */
28+
readonly /*comment*/ final class ReadOnlyFinalClass {}
29+
30+
/* testAbstractReadonlyClass */
31+
abstract readonly class AbstractReadOnlyClass {}
32+
33+
/* testReadonlyAbstractClass */
34+
readonly
35+
abstract
36+
class ReadOnlyAbstractClass {}
37+
38+
/* testWithCommentsAndNewLines */
39+
abstract
40+
/* comment */
41+
class ClassWithCommentsAndNewLines {}
42+
43+
/* testWithDocblockWithoutProperties */
44+
/**
45+
* Class docblock.
46+
*
47+
* @package SomePackage
48+
*
49+
* @phpcs:disable Standard.Cat.SniffName -- Just because.
50+
*/
51+
class ClassWithDocblock {}
52+
53+
/* testParseErrorAbstractFinal */
54+
final /* comment */
55+
56+
abstract // Intentional parse error, class cannot both be final and abstract.
57+
58+
class AbstractFinal {}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
<?php
2+
/**
3+
* Tests for the \PHP_CodeSniffer\Files\File:getClassProperties method.
4+
*
5+
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
6+
* @copyright 2022 Squiz Pty Ltd (ABN 77 084 670 600)
7+
* @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8+
*/
9+
10+
namespace PHP_CodeSniffer\Tests\Core\File;
11+
12+
use PHP_CodeSniffer\Tests\Core\AbstractMethodUnitTest;
13+
14+
class GetClassPropertiesTest extends AbstractMethodUnitTest
15+
{
16+
17+
18+
/**
19+
* Test receiving an expected exception when a non class token is passed.
20+
*
21+
* @param string $testMarker The comment which prefaces the target token in the test file.
22+
* @param array $tokenType The type of token to look for after the marker.
23+
*
24+
* @dataProvider dataNotAClassException
25+
*
26+
* @expectedException PHP_CodeSniffer\Exceptions\RuntimeException
27+
* @expectedExceptionMessage $stackPtr must be of type T_CLASS
28+
*
29+
* @return void
30+
*/
31+
public function testNotAClassException($testMarker, $tokenType)
32+
{
33+
$target = $this->getTargetToken($testMarker, $tokenType);
34+
self::$phpcsFile->getClassProperties($target);
35+
36+
}//end testNotAClassException()
37+
38+
39+
/**
40+
* Data provider.
41+
*
42+
* @see testNotAClassException() For the array format.
43+
*
44+
* @return array
45+
*/
46+
public function dataNotAClassException()
47+
{
48+
return [
49+
'interface' => [
50+
'/* testNotAClass */',
51+
\T_INTERFACE,
52+
],
53+
'anon-class' => [
54+
'/* testAnonClass */',
55+
\T_ANON_CLASS,
56+
],
57+
'enum' => [
58+
'/* testEnum */',
59+
\T_ENUM,
60+
],
61+
];
62+
63+
}//end dataNotAClassException()
64+
65+
66+
/**
67+
* Test retrieving the properties for a class declaration.
68+
*
69+
* @param string $testMarker The comment which prefaces the target token in the test file.
70+
* @param array $expected Expected function output.
71+
*
72+
* @dataProvider dataGetClassProperties
73+
*
74+
* @return void
75+
*/
76+
public function testGetClassProperties($testMarker, $expected)
77+
{
78+
$class = $this->getTargetToken($testMarker, \T_CLASS);
79+
$result = self::$phpcsFile->getClassProperties($class);
80+
$this->assertSame($expected, $result);
81+
82+
}//end testGetClassProperties()
83+
84+
85+
/**
86+
* Data provider.
87+
*
88+
* @see testGetClassProperties() For the array format.
89+
*
90+
* @return array
91+
*/
92+
public function dataGetClassProperties()
93+
{
94+
return [
95+
'no-properties' => [
96+
'/* testClassWithoutProperties */',
97+
[
98+
'is_abstract' => false,
99+
'is_final' => false,
100+
'is_readonly' => false,
101+
],
102+
],
103+
'abstract' => [
104+
'/* testAbstractClass */',
105+
[
106+
'is_abstract' => true,
107+
'is_final' => false,
108+
'is_readonly' => false,
109+
],
110+
],
111+
'final' => [
112+
'/* testFinalClass */',
113+
[
114+
'is_abstract' => false,
115+
'is_final' => true,
116+
'is_readonly' => false,
117+
],
118+
],
119+
'readonly' => [
120+
'/* testReadonlyClass */',
121+
[
122+
'is_abstract' => false,
123+
'is_final' => false,
124+
'is_readonly' => true,
125+
],
126+
],
127+
'final-readonly' => [
128+
'/* testFinalReadonlyClass */',
129+
[
130+
'is_abstract' => false,
131+
'is_final' => true,
132+
'is_readonly' => true,
133+
],
134+
],
135+
'readonly-final' => [
136+
'/* testReadonlyFinalClass */',
137+
[
138+
'is_abstract' => false,
139+
'is_final' => true,
140+
'is_readonly' => true,
141+
],
142+
],
143+
'abstract-readonly' => [
144+
'/* testAbstractReadonlyClass */',
145+
[
146+
'is_abstract' => true,
147+
'is_final' => false,
148+
'is_readonly' => true,
149+
],
150+
],
151+
'readonly-abstract' => [
152+
'/* testReadonlyAbstractClass */',
153+
[
154+
'is_abstract' => true,
155+
'is_final' => false,
156+
'is_readonly' => true,
157+
],
158+
],
159+
'comments-and-new-lines' => [
160+
'/* testWithCommentsAndNewLines */',
161+
[
162+
'is_abstract' => true,
163+
'is_final' => false,
164+
'is_readonly' => false,
165+
],
166+
],
167+
'no-properties-with-docblock' => [
168+
'/* testWithDocblockWithoutProperties */',
169+
[
170+
'is_abstract' => false,
171+
'is_final' => false,
172+
'is_readonly' => false,
173+
],
174+
],
175+
'abstract-final-parse-error' => [
176+
'/* testParseErrorAbstractFinal */',
177+
[
178+
'is_abstract' => true,
179+
'is_final' => true,
180+
'is_readonly' => false,
181+
],
182+
],
183+
];
184+
185+
}//end dataGetClassProperties()
186+
187+
188+
}//end class

0 commit comments

Comments
 (0)