Skip to content

Commit a12284e

Browse files
committed
Merge branch '2.10' of github.com:sirbrillig/phpcs-variable-analysis into 2.10
2 parents d2d1d7e + bcb9c56 commit a12284e

File tree

6 files changed

+162
-37
lines changed

6 files changed

+162
-37
lines changed

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problemati
1616

1717
VariableAnalysis requires PHP 5.4 or higher and [PHP CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) version 3.5.0 or higher.
1818

19-
It also requires [PHPCSUtils](https://phpcsutils.com/) which must be installed as a PHPCS standard. If you are using composer, this will be done automatically (see below).
20-
2119
### With PHPCS Composer Installer
2220

2321
This is the easiest method.
@@ -48,17 +46,15 @@ It should just work after that!
4846

4947
Do ensure that PHP_CodeSniffer's version matches our [requirements](#requirements).
5048

51-
2. Install PHPCSUtils (required by this sniff). Download either the zip or tar.gz file from [the PHPCSUtils latest release page](https://github.com/PHPCSStandards/PHPCSUtils/releases/latest). Expand the file and rename the resulting directory to `phpcsutils`. Move the directory to a place where you'd like to keep all your PHPCS standards.
52-
53-
3. Install VariableAnalysis. Download either the zip or tar.gz file from [the VariableAnalysis latest release page](https://github.com/sirbrillig/phpcs-variable-analysis/releases/latest). Expand the file and rename the resulting directory to `phpcs-variable-analysis`. Move the directory to a place where you'd like to keep all your PHPCS standards.
49+
2. Install VariableAnalysis. Download either the zip or tar.gz file from [the VariableAnalysis latest release page](https://github.com/sirbrillig/phpcs-variable-analysis/releases/latest). Expand the file and rename the resulting directory to `phpcs-variable-analysis`. Move the directory to a place where you'd like to keep all your PHPCS standards.
5450

55-
4. Add the paths of the newly installed standards to the [PHP_CodeSniffer installed_paths configuration](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-installed-standard-paths). The following command should append the new standards to your existing standards (be sure to supply the actual paths to the directories you created above).
51+
3. Add the paths of the newly installed standards to the [PHP_CodeSniffer installed_paths configuration](https://github.com/squizlabs/PHP_CodeSniffer/wiki/Configuration-Options#setting-the-installed-standard-paths). The following command should append the new standards to your existing standards (be sure to supply the actual paths to the directories you created above).
5652

57-
phpcs --config-set installed_paths "$(phpcs --config-show|grep installed_paths|awk '{ print $2 }'),/path/to/phpcsutils,/path/to/phpcs-variable-analysis"
53+
phpcs --config-set installed_paths "$(phpcs --config-show|grep installed_paths|awk '{ print $2 }'),/path/to/phpcs-variable-analysis"
5854

5955
If you do not have any other standards installed, you can do this more easily (again, be sure to supply the actual paths):
6056

61-
phpcs --config-set installed_paths /path/to/phpcsutils,/path/to/phpcs-variable-analysis
57+
phpcs --config-set installed_paths /path/to/phpcs-variable-analysis
6258

6359
## Customization
6460

VariableAnalysis/Lib/Helpers.php

Lines changed: 145 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use VariableAnalysis\Lib\ScopeType;
88
use VariableAnalysis\Lib\VariableInfo;
99
use PHP_CodeSniffer\Util\Tokens;
10-
use PHPCSUtils\Utils\FunctionDeclarations;
1110

1211
class Helpers {
1312
/**
@@ -160,7 +159,7 @@ public static function getFunctionIndexForFunctionArgument(File $phpcsFile, $sta
160159
T_FUNCTION,
161160
T_CLOSURE,
162161
];
163-
if (!in_array($functionToken['code'], $functionTokenTypes, true) && ! FunctionDeclarations::isArrowFunction($phpcsFile, $functionPtr)) {
162+
if (!in_array($functionToken['code'], $functionTokenTypes, true) && ! self::isArrowFunction($phpcsFile, $functionPtr)) {
164163
return null;
165164
}
166165
return $functionPtr;
@@ -412,7 +411,7 @@ private static function getStartOfTokenScope(File $phpcsFile, $stackPtr) {
412411
T_CLOSURE,
413412
];
414413
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
415-
if (in_array($scopeCode, $functionTokenTypes, true) || FunctionDeclarations::isArrowFunction($phpcsFile, $scopePtr)) {
414+
if (in_array($scopeCode, $functionTokenTypes, true) || self::isArrowFunction($phpcsFile, $scopePtr)) {
416415
return $scopePtr;
417416
}
418417
if (isset(Tokens::$ooScopeTokens[$scopeCode]) === true) {
@@ -447,7 +446,7 @@ public static function isTokenInsideArrowFunctionDefinition(File $phpcsFile, $st
447446
return false;
448447
}
449448
$openParenPtr = $openParenIndices[0];
450-
return FunctionDeclarations::isArrowFunction($phpcsFile, $openParenPtr - 1);
449+
return self::isArrowFunction($phpcsFile, $openParenPtr - 1);
451450
}
452451

453452
/**
@@ -461,7 +460,7 @@ public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPt
461460
if (! is_int($arrowFunctionIndex)) {
462461
return null;
463462
}
464-
$arrowFunctionInfo = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
463+
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
465464
if (! $arrowFunctionInfo) {
466465
return null;
467466
}
@@ -484,13 +483,150 @@ private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr
484483
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
485484
for ($index = $stackPtr - 1; $index > $enclosingScopeIndex; $index--) {
486485
$token = $tokens[$index];
487-
if ($token['content'] === 'fn' && FunctionDeclarations::isArrowFunction($phpcsFile, $index)) {
486+
if ($token['content'] === 'fn' && self::isArrowFunction($phpcsFile, $index)) {
488487
return $index;
489488
}
490489
}
491490
return null;
492491
}
493492

493+
/**
494+
* @param File $phpcsFile
495+
* @param int $stackPtr
496+
*
497+
* @return bool
498+
*/
499+
public static function isArrowFunction(File $phpcsFile, $stackPtr) {
500+
$tokens = $phpcsFile->getTokens();
501+
if (defined('T_FN') && $tokens[$stackPtr]['code'] === T_FN) {
502+
return true;
503+
}
504+
if ($tokens[$stackPtr]['content'] !== 'fn') {
505+
return false;
506+
}
507+
// Make sure next non-space token is an open parenthesis
508+
$openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
509+
if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) {
510+
return false;
511+
}
512+
// Find the associated close parenthesis
513+
$closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer'];
514+
// Make sure the next token is a fat arrow
515+
$fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true);
516+
if (! is_int($fatArrowIndex)) {
517+
return false;
518+
}
519+
if ($tokens[$fatArrowIndex]['code'] !== T_DOUBLE_ARROW && $tokens[$fatArrowIndex]['type'] !== 'T_FN_ARROW') {
520+
return false;
521+
}
522+
return true;
523+
}
524+
525+
/**
526+
* @param File $phpcsFile
527+
* @param int $stackPtr
528+
*
529+
* @return ?array
530+
*/
531+
public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr) {
532+
$tokens = $phpcsFile->getTokens();
533+
if (defined('T_FN') && $tokens[$stackPtr]['code'] === T_FN) {
534+
return [
535+
'scope_opener' => $tokens[$stackPtr]['scope_opener'],
536+
'scope_closer' => $tokens[$stackPtr]['scope_closer'],
537+
];
538+
}
539+
if ($tokens[$stackPtr]['content'] !== 'fn') {
540+
return null;
541+
}
542+
// Make sure next non-space token is an open parenthesis
543+
$openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
544+
if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) {
545+
return null;
546+
}
547+
// Find the associated close parenthesis
548+
$closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer'];
549+
// Make sure the next token is a fat arrow
550+
$fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true);
551+
if (! is_int($fatArrowIndex)) {
552+
return null;
553+
}
554+
if ($tokens[$fatArrowIndex]['code'] !== T_DOUBLE_ARROW && $tokens[$fatArrowIndex]['type'] !== 'T_FN_ARROW') {
555+
return null;
556+
}
557+
// Find the scope closer
558+
$endScopeTokens = [
559+
T_COMMA,
560+
T_SEMICOLON,
561+
T_CLOSE_PARENTHESIS,
562+
T_CLOSE_CURLY_BRACKET,
563+
T_CLOSE_SHORT_ARRAY,
564+
];
565+
$scopeCloserIndex = $phpcsFile->findNext($endScopeTokens, $fatArrowIndex + 1);
566+
if (! is_int($scopeCloserIndex)) {
567+
return null;
568+
}
569+
return [
570+
'scope_opener' => $stackPtr,
571+
'scope_closer' => $scopeCloserIndex,
572+
];
573+
}
574+
575+
/**
576+
* Return a list of indices for variables assigned within a list assignment
577+
*
578+
* @param File $phpcsFile
579+
* @param int $listOpenerIndex
580+
*
581+
* @return ?array
582+
*/
583+
public static function getListAssignments(File $phpcsFile, $listOpenerIndex) {
584+
$tokens = $phpcsFile->getTokens();
585+
self::debug('getListAssignments', $listOpenerIndex, $tokens[$listOpenerIndex]);
586+
$closePtr = null;
587+
if (isset($tokens[$listOpenerIndex]['parenthesis_closer'])) {
588+
$closePtr = $tokens[$listOpenerIndex]['parenthesis_closer'];
589+
}
590+
if (isset($tokens[$listOpenerIndex]['bracket_closer'])) {
591+
$closePtr = $tokens[$listOpenerIndex]['bracket_closer'];
592+
}
593+
if (! $closePtr) {
594+
return null;
595+
}
596+
597+
$assignPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $closePtr + 1, null, true);
598+
if (! is_int($assignPtr) || $tokens[$assignPtr]['code'] !== T_EQUAL) {
599+
// If we are nested inside a destructured assignment, we are also an assignment
600+
$parents = isset($tokens[$listOpenerIndex]['nested_parenthesis']) ? $tokens[$listOpenerIndex]['nested_parenthesis'] : [];
601+
// There's no record of nested brackets for short lists; we'll have to find the parent ourselves
602+
$parentSquareBracket = Helpers::findContainingOpeningSquareBracket($phpcsFile, $listOpenerIndex);
603+
if (is_int($parentSquareBracket)) {
604+
$parents[$parentSquareBracket] = 0; // We don't actually need the closing paren
605+
}
606+
$nestedAssignments = null;
607+
foreach (array_reverse($parents, true) as $openParen => $closeParen) {
608+
$nestedAssignments = self::getListAssignments($phpcsFile, $openParen);
609+
}
610+
if ($nestedAssignments === null) {
611+
return null;
612+
}
613+
}
614+
615+
$variablePtrs = [];
616+
617+
$currentPtr = $listOpenerIndex;
618+
$variablePtr = 0;
619+
while ($currentPtr < $closePtr && is_int($variablePtr)) {
620+
$variablePtr = $phpcsFile->findNext([T_VARIABLE], $currentPtr + 1, $closePtr);
621+
if (is_int($variablePtr)) {
622+
$variablePtrs[] = $variablePtr;
623+
}
624+
$currentPtr++;
625+
}
626+
627+
return $variablePtrs;
628+
}
629+
494630
/**
495631
* @param File $phpcsFile
496632
* @param int $stackPtr
@@ -665,8 +801,8 @@ public static function getScopeCloseForScopeOpen(File $phpcsFile, $scopeStartInd
665801
$tokens = $phpcsFile->getTokens();
666802
$scopeCloserIndex = isset($tokens[$scopeStartIndex]['scope_closer']) ? $tokens[$scopeStartIndex]['scope_closer'] : null;
667803

668-
if (FunctionDeclarations::isArrowFunction($phpcsFile, $scopeStartIndex)) {
669-
$arrowFunctionInfo = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $scopeStartIndex);
804+
if (self::isArrowFunction($phpcsFile, $scopeStartIndex)) {
805+
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $scopeStartIndex);
670806
$scopeCloserIndex = $arrowFunctionInfo ? $arrowFunctionInfo['scope_closer'] : $scopeCloserIndex;
671807
}
672808

@@ -773,7 +909,7 @@ public static function getFunctionIndexForFunctionCallArgument(File $phpcsFile,
773909
if (! is_int($functionPtr) || ! isset($tokens[$functionPtr]['code'])) {
774910
return null;
775911
}
776-
if ($tokens[$functionPtr]['code'] === 'function' || ($tokens[$functionPtr]['content'] === 'fn' && FunctionDeclarations::isArrowFunction($phpcsFile, $functionPtr))) {
912+
if ($tokens[$functionPtr]['code'] === 'function' || ($tokens[$functionPtr]['content'] === 'fn' && self::isArrowFunction($phpcsFile, $functionPtr))) {
777913
return null;
778914
}
779915
return $functionPtr;

VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
use PHP_CodeSniffer\Sniffs\Sniff;
1111
use PHP_CodeSniffer\Files\File;
1212
use PHP_CodeSniffer\Util\Tokens;
13-
use PHPCSUtils\Utils\Lists;
14-
use PHPCSUtils\Utils\FunctionDeclarations;
1513

1614
class VariableAnalysisSniff implements Sniff {
1715
/**
@@ -239,7 +237,7 @@ public function process(File $phpcsFile, $stackPtr) {
239237
return;
240238
}
241239
if (in_array($token['code'], $scopeStartTokenTypes, true)
242-
|| FunctionDeclarations::isArrowFunction($phpcsFile, $stackPtr)
240+
|| Helpers::isArrowFunction($phpcsFile, $stackPtr)
243241
) {
244242
Helpers::debug('found scope condition', $token);
245243
$this->recordScopeStartAndEnd($phpcsFile, $stackPtr);
@@ -987,13 +985,12 @@ protected function processVariableAsListShorthandAssignment(File $phpcsFile, $st
987985
}
988986

989987
// OK, we're a [ ... ] construct... are we being assigned to?
990-
try {
991-
$assignments = Lists::getAssignments($phpcsFile, $openPtr);
992-
} catch (\Exception $error) {
988+
$assignments = Helpers::getListAssignments($phpcsFile, $openPtr);
989+
if (! $assignments) {
993990
return false;
994991
}
995-
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
996-
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
992+
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, $assignment) use ($stackPtr) {
993+
if ($assignment === $stackPtr) {
997994
return $assignment;
998995
}
999996
return $thisAssignment;
@@ -1030,13 +1027,12 @@ protected function processVariableAsListAssignment(File $phpcsFile, $stackPtr, $
10301027
}
10311028

10321029
// OK, we're a list (...) construct... are we being assigned to?
1033-
try {
1034-
$assignments = Lists::getAssignments($phpcsFile, $prevPtr);
1035-
} catch (\Exception $error) {
1030+
$assignments = Helpers::getListAssignments($phpcsFile, $prevPtr);
1031+
if (! $assignments) {
10361032
return false;
10371033
}
1038-
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
1039-
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
1034+
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, $assignment) use ($stackPtr) {
1035+
if ($assignment === $stackPtr) {
10401036
return $assignment;
10411037
}
10421038
return $thisAssignment;

VariableAnalysis/ruleset.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,4 @@
22
<ruleset name="VariableAnalysis">
33
<description>Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problematic variable use.</description>
44
<rule ref="VariableAnalysis.CodeAnalysis.VariableAnalysis"/>
5-
<!-- PHPCSUtils is required for this sniff to operate -->
6-
<rule ref="PHPCSUtils"/>
75
</ruleset>

composer.circleci.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@
3838
},
3939
"require" : {
4040
"php" : ">=5.4.0",
41-
"squizlabs/php_codesniffer": "^3.1",
42-
"phpcsstandards/phpcsutils": "^1.0"
41+
"squizlabs/php_codesniffer": "^3.1"
4342
},
4443
"require-dev": {
4544
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.5 || ^7.0 || ^8.0"

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@
4040
},
4141
"require" : {
4242
"php" : ">=5.4.0",
43-
"squizlabs/php_codesniffer": "^3.5",
44-
"phpcsstandards/phpcsutils": "^1.0"
43+
"squizlabs/php_codesniffer": "^3.5"
4544
},
4645
"require-dev": {
4746
"phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0",
4847
"sirbrillig/phpcs-import-detection": "^1.1",
4948
"limedeck/phpunit-detailed-printer": "^3.1 || ^4.0 || ^5.0",
50-
"phpstan/phpstan": "^0.11.8"
49+
"phpstan/phpstan": "^0.11.8",
50+
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0"
5151
}
5252
}

0 commit comments

Comments
 (0)