Skip to content

Commit 5f03314

Browse files
authored
Support constructor promotion (sirbrillig#266)
* Add test for constructor promotion * Support constructor promotion in parameters * Fix linting errors * Add counterexample test of constructor promotion * Turn off allowUnusedParametersBeforeUsed in ClassWithMembers test * Fix token checks in isConstructorPromotion
1 parent 1087f11 commit 5f03314

File tree

4 files changed

+73
-0
lines changed

4 files changed

+73
-0
lines changed

Tests/VariableAnalysisSniff/VariableAnalysisTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ public function testClassWithMembersWarnings()
187187
{
188188
$fixtureFile = $this->getFixture('ClassWithMembersFixture.php');
189189
$phpcsFile = $this->prepareLocalFileForSniffs($fixtureFile);
190+
$phpcsFile->ruleset->setSniffProperty(
191+
'VariableAnalysis\Sniffs\CodeAnalysis\VariableAnalysisSniff',
192+
'allowUnusedParametersBeforeUsed',
193+
'false'
194+
);
190195
$phpcsFile->process();
191196
$lines = $this->getWarningLineNumbersFromFile($phpcsFile);
192197
$expectedWarnings = [
@@ -202,6 +207,8 @@ public function testClassWithMembersWarnings()
202207
18,
203208
19,
204209
66,
210+
115,
211+
116,
205212
];
206213
$this->assertSame($expectedWarnings, $lines);
207214
}

Tests/VariableAnalysisSniff/fixtures/ClassWithMembersFixture.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,18 @@ function method_with_static_assigned_var_inside_block() {
108108
}
109109
}
110110
}
111+
112+
class ClassWithConstructorPromotion {
113+
public function __construct(
114+
public string $name = 'Brent',
115+
$unused, // Unused variable $unused
116+
string $unused2, // Unused variable $unused2
117+
public string $role,
118+
private string $role2,
119+
protected string $role3,
120+
public $nickname,
121+
private $nickname2,
122+
protected $nickname3
123+
) {
124+
}
125+
}

VariableAnalysis/Lib/Helpers.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1278,4 +1278,49 @@ public static function getForLoopForIncrementVariable($stackPtr, $forLoops)
12781278
}
12791279
return null;
12801280
}
1281+
1282+
/**
1283+
* Return true if the token looks like constructor promotion.
1284+
*
1285+
* Call on a parameter variable token only.
1286+
*
1287+
* @param File $phpcsFile
1288+
* @param int $stackPtr
1289+
*
1290+
* @return bool
1291+
*/
1292+
public static function isConstructorPromotion(File $phpcsFile, $stackPtr)
1293+
{
1294+
$functionIndex = self::getFunctionIndexForFunctionParameter($phpcsFile, $stackPtr);
1295+
if (! $functionIndex) {
1296+
return false;
1297+
}
1298+
1299+
$tokens = $phpcsFile->getTokens();
1300+
1301+
// If the previous token is a visibility keyword, this is constructor
1302+
// promotion. eg: `public $foobar`.
1303+
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), $functionIndex, true);
1304+
if (! is_int($prev)) {
1305+
return false;
1306+
}
1307+
$prevToken = $tokens[$prev];
1308+
if (in_array($prevToken['code'], Tokens::$scopeModifiers, true)) {
1309+
return true;
1310+
}
1311+
1312+
// If the previous token is not a visibility keyword, but the one before it
1313+
// is, the previous token was probably a typehint and this is constructor
1314+
// promotion. eg: `public boolean $foobar`.
1315+
$prev = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($prev - 1), $functionIndex, true);
1316+
if (! is_int($prev)) {
1317+
return false;
1318+
}
1319+
$prevToken = $tokens[$prev];
1320+
if (in_array($prevToken['code'], Tokens::$scopeModifiers, true)) {
1321+
return true;
1322+
}
1323+
1324+
return false;
1325+
}
12811326
}

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,12 @@ protected function processVariableAsFunctionParameter(File $phpcsFile, $stackPtr
761761
Helpers::debug('processVariableAsFunctionParameter optional with default');
762762
$this->markVariableAssignment($varName, $stackPtr, $functionPtr);
763763
}
764+
765+
// Are we using constructor promotion? If so, that counts as both definition and use.
766+
if (Helpers::isConstructorPromotion($phpcsFile, $stackPtr)) {
767+
Helpers::debug('processVariableAsFunctionParameter constructor promotion');
768+
$this->markVariableRead($varName, $stackPtr, $outerScope);
769+
}
764770
}
765771

766772
/**

0 commit comments

Comments
 (0)