Skip to content

Commit 1899948

Browse files
committed
minor #4593 Ensure compatibility with PHP 7.4 typed properties (julienfalque)
This PR was merged into the 2.15 branch. Discussion ---------- Ensure compatibility with PHP 7.4 typed properties Related to #4382. Commits ------- 3cd398e Ensure compatibility with PHP 7.4 typed properties
2 parents 4d11c73 + 3cd398e commit 1899948

18 files changed

+493
-20
lines changed

README.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,8 @@ Choose from the list of available rules:
10651065

10661066
* **no_null_property_initialization** [@PhpCsFixer]
10671067

1068-
Properties MUST not be explicitly initialized with ``null``.
1068+
Properties MUST not be explicitly initialized with ``null`` except when
1069+
they have a type declaration (PHP 7.4).
10691070

10701071
* **no_php4_constructor**
10711072

@@ -1169,7 +1170,7 @@ Choose from the list of available rules:
11691170

11701171
Properties should be set to ``null`` instead of using ``unset``.
11711172

1172-
*Risky rule: changing variables to ``null`` instead of unsetting them will mean they still show up when looping over class variables.*
1173+
*Risky rule: changing variables to ``null`` instead of unsetting them will mean they still show up when looping over class variables. With PHP 7.4, this rule might introduce ``null`` assignments to property whose type declaration does not allow it.*
11731174

11741175
* **no_unused_imports** [@Symfony, @PhpCsFixer]
11751176

src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use PhpCsFixer\FixerDefinition\CodeSample;
2222
use PhpCsFixer\FixerDefinition\FixerDefinition;
2323
use PhpCsFixer\Preg;
24+
use PhpCsFixer\Tokenizer\CT;
2425
use PhpCsFixer\Tokenizer\Token;
2526
use PhpCsFixer\Tokenizer\Tokens;
2627
use PhpCsFixer\Tokenizer\TokensAnalyzer;
@@ -228,7 +229,7 @@ private function fixSpaceBelowClassMethod(Tokens $tokens, $classEndIndex, $eleme
228229
*/
229230
private function fixSpaceAboveClassElement(Tokens $tokens, $classStartIndex, $elementIndex)
230231
{
231-
static $methodAttr = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC];
232+
static $methodAttr = [T_PRIVATE, T_PROTECTED, T_PUBLIC, T_ABSTRACT, T_FINAL, T_STATIC, T_STRING, T_NS_SEPARATOR, T_VAR, CT::T_NULLABLE_TYPE];
232233

233234
$nonWhiteAbove = null;
234235

src/Fixer/ClassNotation/NoNullPropertyInitializationFixer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class NoNullPropertyInitializationFixer extends AbstractFixer
2828
public function getDefinition()
2929
{
3030
return new FixerDefinition(
31-
'Properties MUST not be explicitly initialized with `null`.',
31+
'Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4).',
3232
[
3333
new CodeSample(
3434
'<?php

src/Fixer/ClassNotation/SingleClassElementPerStatementFixer.php

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
8787
continue; // not in configuration
8888
}
8989

90-
$this->fixElement($tokens, $index);
90+
$this->fixElement($tokens, $element['type'], $index);
9191
}
9292
}
9393

@@ -108,9 +108,10 @@ protected function createConfigurationDefinition()
108108
}
109109

110110
/**
111-
* @param int $index
111+
* @param string $type
112+
* @param int $index
112113
*/
113-
private function fixElement(Tokens $tokens, $index)
114+
private function fixElement(Tokens $tokens, $type, $index)
114115
{
115116
$tokensAnalyzer = new TokensAnalyzer($tokens);
116117
$repeatIndex = $index;
@@ -142,16 +143,18 @@ private function fixElement(Tokens $tokens, $index)
142143
$start = $tokens->getPrevTokenOfKind($index, [';', '{', '}']);
143144
$this->expandElement(
144145
$tokens,
146+
$type,
145147
$tokens->getNextMeaningfulToken($start),
146148
$tokens->getNextTokenOfKind($index, [';'])
147149
);
148150
}
149151

150152
/**
151-
* @param int $startIndex
152-
* @param int $endIndex
153+
* @param string $type
154+
* @param int $startIndex
155+
* @param int $endIndex
153156
*/
154-
private function expandElement(Tokens $tokens, $startIndex, $endIndex)
157+
private function expandElement(Tokens $tokens, $type, $startIndex, $endIndex)
155158
{
156159
$divisionContent = null;
157160
if ($tokens[$startIndex - 1]->isWhitespace()) {
@@ -191,31 +194,37 @@ private function expandElement(Tokens $tokens, $startIndex, $endIndex)
191194
}
192195

193196
// collect modifiers
194-
$sequence = $this->getModifiersSequences($tokens, $startIndex, $endIndex);
197+
$sequence = $this->getModifiersSequences($tokens, $type, $startIndex, $endIndex);
195198
$tokens->insertAt($i + 2, $sequence);
196199
}
197200
}
198201

199202
/**
200-
* @param int $startIndex
201-
* @param int $endIndex
203+
* @param string $type
204+
* @param int $startIndex
205+
* @param int $endIndex
202206
*
203207
* @return Token[]
204208
*/
205-
private function getModifiersSequences(Tokens $tokens, $startIndex, $endIndex)
209+
private function getModifiersSequences(Tokens $tokens, $type, $startIndex, $endIndex)
206210
{
211+
if ('property' === $type) {
212+
$tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_VAR, T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE];
213+
} else {
214+
$tokenKinds = [T_PUBLIC, T_PROTECTED, T_PRIVATE, T_CONST];
215+
}
216+
207217
$sequence = [];
208218
for ($i = $startIndex; $i < $endIndex - 1; ++$i) {
209-
if ($tokens[$i]->isWhitespace() || $tokens[$i]->isComment()) {
219+
if ($tokens[$i]->isComment()) {
210220
continue;
211221
}
212222

213-
if (!$tokens[$i]->isGivenKind([T_PUBLIC, T_PROTECTED, T_PRIVATE, T_STATIC, T_CONST, T_VAR])) {
223+
if (!$tokens[$i]->isWhitespace() && !$tokens[$i]->isGivenKind($tokenKinds)) {
214224
break;
215225
}
216226

217227
$sequence[] = clone $tokens[$i];
218-
$sequence[] = new Token([T_WHITESPACE, ' ']);
219228
}
220229

221230
return $sequence;

src/Fixer/ClassNotation/VisibilityRequiredFixer.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use PhpCsFixer\FixerDefinition\FixerDefinition;
2323
use PhpCsFixer\FixerDefinition\VersionSpecification;
2424
use PhpCsFixer\FixerDefinition\VersionSpecificCodeSample;
25+
use PhpCsFixer\Tokenizer\CT;
2526
use PhpCsFixer\Tokenizer\Token;
2627
use PhpCsFixer\Tokenizer\Tokens;
2728
use PhpCsFixer\Tokenizer\TokensAnalyzer;
@@ -115,18 +116,31 @@ protected function applyFix(\SplFileInfo $file, Tokens $tokens)
115116
$abstractFinalIndex = null;
116117
$visibilityIndex = null;
117118
$staticIndex = null;
119+
$typeIndex = null;
118120
$prevIndex = $tokens->getPrevMeaningfulToken($index);
119-
while ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR])) {
121+
122+
$expectedKinds = [T_ABSTRACT, T_FINAL, T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC, T_VAR];
123+
if ('property' === $element['type']) {
124+
$expectedKinds = array_merge($expectedKinds, [T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE]);
125+
}
126+
127+
while ($tokens[$prevIndex]->isGivenKind($expectedKinds)) {
120128
if ($tokens[$prevIndex]->isGivenKind([T_ABSTRACT, T_FINAL])) {
121129
$abstractFinalIndex = $prevIndex;
122130
} elseif ($tokens[$prevIndex]->isGivenKind(T_STATIC)) {
123131
$staticIndex = $prevIndex;
132+
} elseif ($tokens[$prevIndex]->isGivenKind([T_STRING, T_NS_SEPARATOR, CT::T_NULLABLE_TYPE])) {
133+
$typeIndex = $prevIndex;
124134
} else {
125135
$visibilityIndex = $prevIndex;
126136
}
127137
$prevIndex = $tokens->getPrevMeaningfulToken($prevIndex);
128138
}
129139

140+
if (null !== $typeIndex) {
141+
$index = $typeIndex;
142+
}
143+
130144
if ($tokens[$prevIndex]->equals(',')) {
131145
continue;
132146
}

src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public function getDefinition()
3434
[new CodeSample("<?php\nunset(\$this->a);\n")],
3535
null,
3636
'Changing variables to `null` instead of unsetting them will mean they still show up '.
37-
'when looping over class variables.'
37+
'when looping over class variables. With PHP 7.4, this rule might introduce `null` assignments to '.
38+
'property whose type declaration does not allow it.'
3839
);
3940
}
4041

src/Tokenizer/Transformer/NullableTypeTransformer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public function process(Tokens $tokens, Token $token, $index)
6363
$prevIndex = $tokens->getPrevMeaningfulToken($index);
6464
$prevToken = $tokens[$prevIndex];
6565

66-
if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON]])) {
66+
if ($prevToken->equalsAny(['(', ',', [CT::T_TYPE_COLON], [T_PRIVATE], [T_PROTECTED], [T_PUBLIC], [T_VAR]])) {
6767
$tokens[$index] = new Token([CT::T_NULLABLE_TYPE, '?']);
6868
}
6969
}

tests/Fixer/ClassNotation/ClassAttributesSeparationFixerTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,17 @@ public function C(); // allowed comment
676676
public function C(); // allowed comment
677677
}',
678678
];
679+
$cases[] = [
680+
'<?php class Foo {
681+
var $a;
682+
683+
var $b;
684+
}',
685+
'<?php class Foo {
686+
var $a;
687+
var $b;
688+
}',
689+
];
679690

680691
return $cases;
681692
}
@@ -1070,4 +1081,39 @@ public abstract function A(){}
10701081
],
10711082
];
10721083
}
1084+
1085+
/**
1086+
* @param string $expected
1087+
* @param null|string $input
1088+
*
1089+
* @dataProvider provideFix74Cases
1090+
* @requires PHP 7.4
1091+
*/
1092+
public function testFix74($expected, $input = null)
1093+
{
1094+
$this->doTest($expected, $input);
1095+
}
1096+
1097+
public function provideFix74Cases()
1098+
{
1099+
yield [
1100+
'<?php
1101+
class Foo {
1102+
private ?int $foo;
1103+
1104+
protected string $bar;
1105+
1106+
public iterable $baz;
1107+
1108+
var ? Foo\Bar $qux;
1109+
}',
1110+
'<?php
1111+
class Foo {
1112+
private ?int $foo;
1113+
protected string $bar;
1114+
public iterable $baz;
1115+
var ? Foo\Bar $qux;
1116+
}',
1117+
];
1118+
}
10731119
}

tests/Fixer/ClassNotation/NoNullPropertyInitializationFixerTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,26 @@ public function providePhp71Cases()
199199
],
200200
];
201201
}
202+
203+
/**
204+
* @param string $expected
205+
* @param null|string $input
206+
*
207+
* @dataProvider provideFix74Cases
208+
* @requires PHP 7.4
209+
*/
210+
public function testFix74($expected, $input = null)
211+
{
212+
$this->doTest($expected, $input);
213+
}
214+
215+
public function provideFix74Cases()
216+
{
217+
yield [
218+
'<?php class Foo { protected ?int $bar = null; }',
219+
];
220+
yield [
221+
'<?php class Foo { protected ? string $bar = null; }',
222+
];
223+
}
202224
}

tests/Fixer/ClassNotation/OrderedClassElementsFixerTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,61 @@ private function privFunc() {}
824824
];
825825
}
826826

827+
/**
828+
* @param string $expected
829+
* @param null|string $input
830+
*
831+
* @dataProvider provideFix74Cases
832+
* @requires PHP 7.4
833+
*/
834+
public function testFix74($expected, $input = null, array $configuration = null)
835+
{
836+
if (null !== $configuration) {
837+
$this->fixer->configure($configuration);
838+
}
839+
840+
$this->doTest($expected, $input);
841+
}
842+
843+
public function provideFix74Cases()
844+
{
845+
yield [
846+
'<?php
847+
class Foo {
848+
public iterable $baz;
849+
var ? Foo\Bar $qux;
850+
protected string $bar;
851+
private ?int $foo;
852+
}',
853+
'<?php
854+
class Foo {
855+
private ?int $foo;
856+
protected string $bar;
857+
public iterable $baz;
858+
var ? Foo\Bar $qux;
859+
}',
860+
];
861+
yield [
862+
'<?php
863+
class Foo {
864+
public string $bar;
865+
public iterable $baz;
866+
public ?int $foo;
867+
public ? Foo\Bar $qux;
868+
}',
869+
'<?php
870+
class Foo {
871+
public iterable $baz;
872+
public ? Foo\Bar $qux;
873+
public string $bar;
874+
public ?int $foo;
875+
}',
876+
[
877+
'sortAlgorithm' => 'alpha',
878+
],
879+
];
880+
}
881+
827882
public function testWrongConfig()
828883
{
829884
$this->expectException(\PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException::class);

0 commit comments

Comments
 (0)