Skip to content

Commit 3b6d390

Browse files
committed
Support for PHP 8.4 properties
- Asymetric visibility supported - final/abstract properties supported - Property hooks should not break any sniff
1 parent b756dce commit 3b6d390

File tree

45 files changed

+730
-275
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+730
-275
lines changed

SlevomatCodingStandard/Helpers/PropertyHelper.php

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@
1414
use function preg_match;
1515
use function preg_replace;
1616
use function sprintf;
17+
use const T_ABSTRACT;
1718
use const T_ANON_CLASS;
1819
use const T_CLOSE_CURLY_BRACKET;
20+
use const T_COMMA;
21+
use const T_FINAL;
1922
use const T_FUNCTION;
2023
use const T_NULLABLE;
2124
use const T_OPEN_CURLY_BRACKET;
22-
use const T_PRIVATE;
23-
use const T_PROTECTED;
24-
use const T_PUBLIC;
25+
use const T_OPEN_PARENTHESIS;
2526
use const T_READONLY;
2627
use const T_SEMICOLON;
2728
use const T_STATIC;
28-
use const T_VAR;
2929

3030
/**
3131
* @internal
@@ -43,11 +43,19 @@ public static function isProperty(File $phpcsFile, int $variablePointer, bool $p
4343
$variablePointer - 1,
4444
);
4545

46+
if (in_array($tokens[$previousPointer]['code'], [T_FINAL, T_ABSTRACT], true)) {
47+
return true;
48+
}
49+
4650
if ($tokens[$previousPointer]['code'] === T_STATIC) {
4751
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousPointer - 1);
4852
}
4953

50-
if (in_array($tokens[$previousPointer]['code'], [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_VAR, T_READONLY], true)) {
54+
if (in_array(
55+
$tokens[$previousPointer]['code'],
56+
[...array_values(Tokens::$scopeModifiers), T_READONLY],
57+
true,
58+
)) {
5159
$constructorPointer = TokenHelper::findPrevious($phpcsFile, T_FUNCTION, $previousPointer - 1);
5260

5361
if ($constructorPointer === null) {
@@ -76,21 +84,85 @@ public static function isProperty(File $phpcsFile, int $variablePointer, bool $p
7684
return false;
7785
}
7886

87+
$previousParenthesisPointer = TokenHelper::findPrevious($phpcsFile, T_OPEN_PARENTHESIS, $variablePointer - 1);
88+
if ($previousParenthesisPointer !== null && $tokens[$previousParenthesisPointer]['parenthesis_closer'] > $variablePointer) {
89+
$previousPointer = TokenHelper::findPreviousEffective($phpcsFile, $previousParenthesisPointer - 1);
90+
if ($previousPointer !== null && in_array($tokens[$previousPointer]['content'], ['get', 'set'], true)) {
91+
// Parameter of property hook
92+
return false;
93+
}
94+
}
95+
96+
$previousCurlyBracketPointer = TokenHelper::findPrevious($phpcsFile, T_OPEN_CURLY_BRACKET, $variablePointer - 1);
97+
if (
98+
$previousCurlyBracketPointer !== null
99+
&& $tokens[$previousCurlyBracketPointer]['bracket_closer'] > $variablePointer
100+
) {
101+
// Variable in content of property hook
102+
if (!array_key_exists('scope_condition', $tokens[$previousCurlyBracketPointer])) {
103+
return false;
104+
}
105+
}
106+
79107
$conditionCode = array_values($tokens[$variablePointer]['conditions'])[count($tokens[$variablePointer]['conditions']) - 1];
80108

81109
return in_array($conditionCode, Tokens::$ooScopeTokens, true);
82110
}
83111

84-
public static function findTypeHint(File $phpcsFile, int $propertyPointer): ?TypeHint
112+
public static function getStartPointer(File $phpcsFile, int $propertyPointer): int
85113
{
86-
$tokens = $phpcsFile->getTokens();
87-
88-
$propertyStartPointer = TokenHelper::findPrevious(
114+
$previousCodeEndPointer = TokenHelper::findPrevious(
89115
$phpcsFile,
90-
[T_PRIVATE, T_PROTECTED, T_PUBLIC, T_VAR, T_STATIC, T_READONLY],
116+
[
117+
// Previous property or constant
118+
T_SEMICOLON,
119+
// Previous method or property with hooks
120+
T_CLOSE_CURLY_BRACKET,
121+
// Start of the class
122+
T_OPEN_CURLY_BRACKET,
123+
// Start of the constructor
124+
T_OPEN_PARENTHESIS,
125+
// Previous parameter in the constructor
126+
T_COMMA,
127+
],
91128
$propertyPointer - 1,
92129
);
93130

131+
$startPointer = TokenHelper::findPreviousEffective($phpcsFile, $propertyPointer - 1, $previousCodeEndPointer);
132+
133+
do {
134+
$possibleStartPointer = TokenHelper::findPrevious(
135+
$phpcsFile,
136+
TokenHelper::PROPERTY_MODIFIERS_TOKEN_CODES,
137+
$startPointer - 1,
138+
$previousCodeEndPointer,
139+
);
140+
141+
if ($possibleStartPointer === null) {
142+
return $startPointer;
143+
}
144+
145+
$startPointer = $possibleStartPointer;
146+
} while (true);
147+
}
148+
149+
public static function getEndPointer(File $phpcsFile, int $propertyPointer): int
150+
{
151+
$tokens = $phpcsFile->getTokens();
152+
153+
$endPointer = TokenHelper::findNext($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $propertyPointer + 1);
154+
155+
return $tokens[$endPointer]['code'] === T_OPEN_CURLY_BRACKET
156+
? $tokens[$endPointer]['bracket_closer']
157+
: $endPointer;
158+
}
159+
160+
public static function findTypeHint(File $phpcsFile, int $propertyPointer): ?TypeHint
161+
{
162+
$tokens = $phpcsFile->getTokens();
163+
164+
$propertyStartPointer = self::getStartPointer($phpcsFile, $propertyPointer);
165+
94166
$typeHintEndPointer = TokenHelper::findPrevious(
95167
$phpcsFile,
96168
TokenHelper::getTypeHintTokenCodes(),

SlevomatCodingStandard/Helpers/TokenHelper.php

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use function array_key_exists;
77
use function array_merge;
88
use function count;
9+
use const T_ABSTRACT;
910
use const T_ANON_CLASS;
1011
use const T_ARRAY;
1112
use const T_BREAK;
@@ -24,6 +25,7 @@
2425
use const T_ENUM;
2526
use const T_EXIT;
2627
use const T_FALSE;
28+
use const T_FINAL;
2729
use const T_FN;
2830
use const T_FUNCTION;
2931
use const T_INTERFACE;
@@ -40,8 +42,11 @@
4042
use const T_PHPCS_IGNORE_FILE;
4143
use const T_PHPCS_SET;
4244
use const T_PRIVATE;
45+
use const T_PRIVATE_SET;
4346
use const T_PROTECTED;
47+
use const T_PROTECTED_SET;
4448
use const T_PUBLIC;
49+
use const T_PUBLIC_SET;
4550
use const T_READONLY;
4651
use const T_RETURN;
4752
use const T_SELF;
@@ -63,6 +68,22 @@
6368
class TokenHelper
6469
{
6570

71+
public const MODIFIERS_TOKEN_CODES = [
72+
T_FINAL,
73+
T_ABSTRACT,
74+
T_VAR,
75+
T_PUBLIC,
76+
T_PUBLIC_SET,
77+
T_PROTECTED,
78+
T_PROTECTED_SET,
79+
T_PRIVATE,
80+
T_PRIVATE_SET,
81+
T_READONLY,
82+
T_STATIC,
83+
];
84+
85+
public const PROPERTY_MODIFIERS_TOKEN_CODES = self::MODIFIERS_TOKEN_CODES;
86+
6687
/** @var array<int, (int|string)> */
6788
public static array $arrayTokenCodes = [
6889
T_ARRAY,
@@ -140,16 +161,6 @@ class TokenHelper
140161
T_FN,
141162
];
142163

143-
/** @var array<int, (int|string)> */
144-
public static array $propertyModifiersTokenCodes = [
145-
T_VAR,
146-
T_PUBLIC,
147-
T_PROTECTED,
148-
T_PRIVATE,
149-
T_READONLY,
150-
T_STATIC,
151-
];
152-
153164
/**
154165
* @param int|string|array<int|string, int|string> $types
155166
*/

SlevomatCodingStandard/Sniffs/Classes/AbstractPropertyConstantAndEnumCaseSpacing.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@
1717
use const T_DOC_COMMENT_OPEN_TAG;
1818
use const T_ENUM_CASE;
1919
use const T_FUNCTION;
20-
use const T_PRIVATE;
21-
use const T_PROTECTED;
22-
use const T_PUBLIC;
23-
use const T_READONLY;
20+
use const T_OPEN_CURLY_BRACKET;
2421
use const T_SEMICOLON;
25-
use const T_STATIC;
2622
use const T_USE;
27-
use const T_VAR;
2823
use const T_VARIABLE;
2924

3025
/**
@@ -60,10 +55,9 @@ public function process(File $phpcsFile, $pointer): int
6055

6156
$classPointer = ClassHelper::getClassPointer($phpcsFile, $pointer);
6257

63-
$semicolonPointer = TokenHelper::findNext($phpcsFile, [T_SEMICOLON], $pointer + 1);
64-
assert($semicolonPointer !== null);
58+
$endPointer = $this->getEndPointer($phpcsFile, $pointer);
6559

66-
$firstOnLinePointer = TokenHelper::findFirstTokenOnNextLine($phpcsFile, $semicolonPointer);
60+
$firstOnLinePointer = TokenHelper::findFirstTokenOnNextLine($phpcsFile, $endPointer);
6761
assert($firstOnLinePointer !== null);
6862

6963
$nextFunctionPointer = TokenHelper::findNext(
@@ -79,14 +73,14 @@ public function process(File $phpcsFile, $pointer): int
7973
return $nextFunctionPointer ?? $firstOnLinePointer;
8074
}
8175

82-
$types = [T_COMMENT, T_DOC_COMMENT_OPEN_TAG, T_ATTRIBUTE, T_ENUM_CASE, T_CONST, T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_READONLY, T_STATIC, T_USE];
76+
$types = [T_COMMENT, T_DOC_COMMENT_OPEN_TAG, T_ATTRIBUTE, T_ENUM_CASE, T_CONST, T_USE, ...TokenHelper::PROPERTY_MODIFIERS_TOKEN_CODES];
8377
$nextPointer = TokenHelper::findNext($phpcsFile, $types, $firstOnLinePointer + 1, $tokens[$classPointer]['scope_closer']);
8478

8579
if (!$this->isNextMemberValid($phpcsFile, $nextPointer)) {
8680
return $nextPointer;
8781
}
8882

89-
$linesBetween = $tokens[$nextPointer]['line'] - $tokens[$semicolonPointer]['line'] - 1;
83+
$linesBetween = $tokens[$nextPointer]['line'] - $tokens[$endPointer]['line'] - 1;
9084
if (in_array($tokens[$nextPointer]['code'], [T_DOC_COMMENT_OPEN_TAG, T_COMMENT, T_ATTRIBUTE], true)) {
9185
$minExpectedLines = $this->minLinesCountBeforeWithComment;
9286
$maxExpectedLines = $this->maxLinesCountBeforeWithComment;
@@ -105,7 +99,7 @@ public function process(File $phpcsFile, $pointer): int
10599
}
106100

107101
if ($linesBetween > $maxExpectedLines) {
108-
$lastPointerOnLine = TokenHelper::findLastTokenOnLine($phpcsFile, $semicolonPointer);
102+
$lastPointerOnLine = TokenHelper::findLastTokenOnLine($phpcsFile, $endPointer);
109103
$firstPointerOnNextLine = TokenHelper::findFirstTokenOnLine($phpcsFile, $nextPointer);
110104

111105
$phpcsFile->fixer->beginChangeset();
@@ -130,4 +124,15 @@ public function process(File $phpcsFile, $pointer): int
130124
return $firstOnLinePointer;
131125
}
132126

127+
private function getEndPointer(File $phpcsFile, int $pointer): int
128+
{
129+
$tokens = $phpcsFile->getTokens();
130+
131+
$endPointer = TokenHelper::findNext($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $pointer + 1);
132+
133+
return $tokens[$endPointer]['code'] === T_OPEN_CURLY_BRACKET
134+
? $tokens[$endPointer]['bracket_closer']
135+
: $endPointer;
136+
}
137+
133138
}

SlevomatCodingStandard/Sniffs/Classes/ClassMemberSpacingSniff.php

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,15 @@
1818
use function in_array;
1919
use function sprintf;
2020
use function str_repeat;
21-
use const T_ABSTRACT;
2221
use const T_AS;
2322
use const T_ATTRIBUTE_END;
2423
use const T_CLOSE_CURLY_BRACKET;
2524
use const T_CONST;
2625
use const T_ENUM_CASE;
27-
use const T_FINAL;
2826
use const T_FUNCTION;
2927
use const T_OPEN_CURLY_BRACKET;
30-
use const T_PRIVATE;
31-
use const T_PROTECTED;
32-
use const T_PUBLIC;
33-
use const T_READONLY;
3428
use const T_SEMICOLON;
35-
use const T_STATIC;
3629
use const T_USE;
37-
use const T_VAR;
3830
use const T_VARIABLE;
3931

4032
class ClassMemberSpacingSniff implements Sniff
@@ -158,11 +150,13 @@ private function findNextMember(File $phpcsFile, int $classPointer, int $previou
158150
{
159151
$tokens = $phpcsFile->getTokens();
160152

153+
$memberTokenCodes = [T_USE, T_CONST, T_FUNCTION, T_ENUM_CASE, ...TokenHelper::PROPERTY_MODIFIERS_TOKEN_CODES];
154+
161155
$memberPointer = $previousMemberPointer;
162156
do {
163157
$memberPointer = TokenHelper::findNext(
164158
$phpcsFile,
165-
[T_USE, T_CONST, T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_READONLY, T_STATIC, T_FUNCTION, T_ENUM_CASE],
159+
$memberTokenCodes,
166160
$memberPointer + 1,
167161
$tokens[$classPointer]['scope_closer'],
168162
);
@@ -175,7 +169,7 @@ private function findNextMember(File $phpcsFile, int $classPointer, int $previou
175169
if (!UseStatementHelper::isTraitUse($phpcsFile, $memberPointer)) {
176170
continue;
177171
}
178-
} elseif (in_array($tokens[$memberPointer]['code'], [T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_READONLY, T_STATIC], true)) {
172+
} elseif (in_array($tokens[$memberPointer]['code'], TokenHelper::PROPERTY_MODIFIERS_TOKEN_CODES, true)) {
179173
$asPointer = TokenHelper::findPreviousEffective($phpcsFile, $memberPointer - 1);
180174
if ($tokens[$asPointer]['code'] === T_AS) {
181175
continue;
@@ -245,17 +239,16 @@ private function getMemberFirstCodePointer(File $phpcsFile, int $memberPointer):
245239
return $memberPointer;
246240
}
247241

242+
$endTokenCodes = [T_SEMICOLON, T_CLOSE_CURLY_BRACKET];
243+
$startOrEndTokenCodes = [...TokenHelper::MODIFIERS_TOKEN_CODES, ...$endTokenCodes];
244+
248245
$firstCodePointer = $memberPointer;
249246
$previousFirstCodePointer = $memberPointer;
250247
do {
251248
/** @var int $firstCodePointer */
252-
$firstCodePointer = TokenHelper::findPrevious(
253-
$phpcsFile,
254-
[T_VAR, T_PUBLIC, T_PROTECTED, T_PRIVATE, T_ABSTRACT, T_FINAL, T_SEMICOLON, T_CLOSE_CURLY_BRACKET],
255-
$firstCodePointer - 1,
256-
);
249+
$firstCodePointer = TokenHelper::findPrevious($phpcsFile, $startOrEndTokenCodes, $firstCodePointer - 1);
257250

258-
if (in_array($tokens[$firstCodePointer]['code'], [T_SEMICOLON, T_CLOSE_CURLY_BRACKET], true)) {
251+
if (in_array($tokens[$firstCodePointer]['code'], $endTokenCodes, true)) {
259252
break;
260253
}
261254

@@ -270,7 +263,11 @@ private function getMemberEndPointer(File $phpcsFile, int $memberPointer): int
270263
{
271264
$tokens = $phpcsFile->getTokens();
272265

273-
if ($tokens[$memberPointer]['code'] === T_USE) {
266+
if (
267+
$tokens[$memberPointer]['code'] === T_USE
268+
// Property with hooks
269+
|| $tokens[$memberPointer]['code'] === T_VARIABLE
270+
) {
274271
$pointer = TokenHelper::findNextLocal($phpcsFile, [T_SEMICOLON, T_OPEN_CURLY_BRACKET], $memberPointer + 1);
275272

276273
return $tokens[$pointer]['code'] === T_OPEN_CURLY_BRACKET

0 commit comments

Comments
 (0)