Skip to content

Commit

Permalink
Merge branch '2.10' of github.com:sirbrillig/phpcs-variable-analysis …
Browse files Browse the repository at this point in the history
…into 2.10
  • Loading branch information
sirbrillig committed Nov 10, 2020
2 parents d2d1d7e + bcb9c56 commit a12284e
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 37 deletions.
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problemati

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

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).

### With PHPCS Composer Installer

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

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

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.

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.
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.

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).
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).

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

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

phpcs --config-set installed_paths /path/to/phpcsutils,/path/to/phpcs-variable-analysis
phpcs --config-set installed_paths /path/to/phpcs-variable-analysis

## Customization

Expand Down
154 changes: 145 additions & 9 deletions VariableAnalysis/Lib/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use VariableAnalysis\Lib\ScopeType;
use VariableAnalysis\Lib\VariableInfo;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\FunctionDeclarations;

class Helpers {
/**
Expand Down Expand Up @@ -160,7 +159,7 @@ public static function getFunctionIndexForFunctionArgument(File $phpcsFile, $sta
T_FUNCTION,
T_CLOSURE,
];
if (!in_array($functionToken['code'], $functionTokenTypes, true) && ! FunctionDeclarations::isArrowFunction($phpcsFile, $functionPtr)) {
if (!in_array($functionToken['code'], $functionTokenTypes, true) && ! self::isArrowFunction($phpcsFile, $functionPtr)) {
return null;
}
return $functionPtr;
Expand Down Expand Up @@ -412,7 +411,7 @@ private static function getStartOfTokenScope(File $phpcsFile, $stackPtr) {
T_CLOSURE,
];
foreach (array_reverse($conditions, true) as $scopePtr => $scopeCode) {
if (in_array($scopeCode, $functionTokenTypes, true) || FunctionDeclarations::isArrowFunction($phpcsFile, $scopePtr)) {
if (in_array($scopeCode, $functionTokenTypes, true) || self::isArrowFunction($phpcsFile, $scopePtr)) {
return $scopePtr;
}
if (isset(Tokens::$ooScopeTokens[$scopeCode]) === true) {
Expand Down Expand Up @@ -447,7 +446,7 @@ public static function isTokenInsideArrowFunctionDefinition(File $phpcsFile, $st
return false;
}
$openParenPtr = $openParenIndices[0];
return FunctionDeclarations::isArrowFunction($phpcsFile, $openParenPtr - 1);
return self::isArrowFunction($phpcsFile, $openParenPtr - 1);
}

/**
Expand All @@ -461,7 +460,7 @@ public static function getContainingArrowFunctionIndex(File $phpcsFile, $stackPt
if (! is_int($arrowFunctionIndex)) {
return null;
}
$arrowFunctionInfo = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $arrowFunctionIndex);
if (! $arrowFunctionInfo) {
return null;
}
Expand All @@ -484,13 +483,150 @@ private static function getPreviousArrowFunctionIndex(File $phpcsFile, $stackPtr
$enclosingScopeIndex = self::findVariableScopeExceptArrowFunctions($phpcsFile, $stackPtr);
for ($index = $stackPtr - 1; $index > $enclosingScopeIndex; $index--) {
$token = $tokens[$index];
if ($token['content'] === 'fn' && FunctionDeclarations::isArrowFunction($phpcsFile, $index)) {
if ($token['content'] === 'fn' && self::isArrowFunction($phpcsFile, $index)) {
return $index;
}
}
return null;
}

/**
* @param File $phpcsFile
* @param int $stackPtr
*
* @return bool
*/
public static function isArrowFunction(File $phpcsFile, $stackPtr) {
$tokens = $phpcsFile->getTokens();
if (defined('T_FN') && $tokens[$stackPtr]['code'] === T_FN) {
return true;
}
if ($tokens[$stackPtr]['content'] !== 'fn') {
return false;
}
// Make sure next non-space token is an open parenthesis
$openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) {
return false;
}
// Find the associated close parenthesis
$closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer'];
// Make sure the next token is a fat arrow
$fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true);
if (! is_int($fatArrowIndex)) {
return false;
}
if ($tokens[$fatArrowIndex]['code'] !== T_DOUBLE_ARROW && $tokens[$fatArrowIndex]['type'] !== 'T_FN_ARROW') {
return false;
}
return true;
}

/**
* @param File $phpcsFile
* @param int $stackPtr
*
* @return ?array
*/
public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr) {
$tokens = $phpcsFile->getTokens();
if (defined('T_FN') && $tokens[$stackPtr]['code'] === T_FN) {
return [
'scope_opener' => $tokens[$stackPtr]['scope_opener'],
'scope_closer' => $tokens[$stackPtr]['scope_closer'],
];
}
if ($tokens[$stackPtr]['content'] !== 'fn') {
return null;
}
// Make sure next non-space token is an open parenthesis
$openParenIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true);
if (! is_int($openParenIndex) || $tokens[$openParenIndex]['code'] !== T_OPEN_PARENTHESIS) {
return null;
}
// Find the associated close parenthesis
$closeParenIndex = $tokens[$openParenIndex]['parenthesis_closer'];
// Make sure the next token is a fat arrow
$fatArrowIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $closeParenIndex + 1, null, true);
if (! is_int($fatArrowIndex)) {
return null;
}
if ($tokens[$fatArrowIndex]['code'] !== T_DOUBLE_ARROW && $tokens[$fatArrowIndex]['type'] !== 'T_FN_ARROW') {
return null;
}
// Find the scope closer
$endScopeTokens = [
T_COMMA,
T_SEMICOLON,
T_CLOSE_PARENTHESIS,
T_CLOSE_CURLY_BRACKET,
T_CLOSE_SHORT_ARRAY,
];
$scopeCloserIndex = $phpcsFile->findNext($endScopeTokens, $fatArrowIndex + 1);
if (! is_int($scopeCloserIndex)) {
return null;
}
return [
'scope_opener' => $stackPtr,
'scope_closer' => $scopeCloserIndex,
];
}

/**
* Return a list of indices for variables assigned within a list assignment
*
* @param File $phpcsFile
* @param int $listOpenerIndex
*
* @return ?array
*/
public static function getListAssignments(File $phpcsFile, $listOpenerIndex) {
$tokens = $phpcsFile->getTokens();
self::debug('getListAssignments', $listOpenerIndex, $tokens[$listOpenerIndex]);
$closePtr = null;
if (isset($tokens[$listOpenerIndex]['parenthesis_closer'])) {
$closePtr = $tokens[$listOpenerIndex]['parenthesis_closer'];
}
if (isset($tokens[$listOpenerIndex]['bracket_closer'])) {
$closePtr = $tokens[$listOpenerIndex]['bracket_closer'];
}
if (! $closePtr) {
return null;
}

$assignPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $closePtr + 1, null, true);
if (! is_int($assignPtr) || $tokens[$assignPtr]['code'] !== T_EQUAL) {
// If we are nested inside a destructured assignment, we are also an assignment
$parents = isset($tokens[$listOpenerIndex]['nested_parenthesis']) ? $tokens[$listOpenerIndex]['nested_parenthesis'] : [];
// There's no record of nested brackets for short lists; we'll have to find the parent ourselves
$parentSquareBracket = Helpers::findContainingOpeningSquareBracket($phpcsFile, $listOpenerIndex);
if (is_int($parentSquareBracket)) {
$parents[$parentSquareBracket] = 0; // We don't actually need the closing paren
}
$nestedAssignments = null;
foreach (array_reverse($parents, true) as $openParen => $closeParen) {
$nestedAssignments = self::getListAssignments($phpcsFile, $openParen);
}
if ($nestedAssignments === null) {
return null;
}
}

$variablePtrs = [];

$currentPtr = $listOpenerIndex;
$variablePtr = 0;
while ($currentPtr < $closePtr && is_int($variablePtr)) {
$variablePtr = $phpcsFile->findNext([T_VARIABLE], $currentPtr + 1, $closePtr);
if (is_int($variablePtr)) {
$variablePtrs[] = $variablePtr;
}
$currentPtr++;
}

return $variablePtrs;
}

/**
* @param File $phpcsFile
* @param int $stackPtr
Expand Down Expand Up @@ -665,8 +801,8 @@ public static function getScopeCloseForScopeOpen(File $phpcsFile, $scopeStartInd
$tokens = $phpcsFile->getTokens();
$scopeCloserIndex = isset($tokens[$scopeStartIndex]['scope_closer']) ? $tokens[$scopeStartIndex]['scope_closer'] : null;

if (FunctionDeclarations::isArrowFunction($phpcsFile, $scopeStartIndex)) {
$arrowFunctionInfo = FunctionDeclarations::getArrowFunctionOpenClose($phpcsFile, $scopeStartIndex);
if (self::isArrowFunction($phpcsFile, $scopeStartIndex)) {
$arrowFunctionInfo = self::getArrowFunctionOpenClose($phpcsFile, $scopeStartIndex);
$scopeCloserIndex = $arrowFunctionInfo ? $arrowFunctionInfo['scope_closer'] : $scopeCloserIndex;
}

Expand Down Expand Up @@ -773,7 +909,7 @@ public static function getFunctionIndexForFunctionCallArgument(File $phpcsFile,
if (! is_int($functionPtr) || ! isset($tokens[$functionPtr]['code'])) {
return null;
}
if ($tokens[$functionPtr]['code'] === 'function' || ($tokens[$functionPtr]['content'] === 'fn' && FunctionDeclarations::isArrowFunction($phpcsFile, $functionPtr))) {
if ($tokens[$functionPtr]['code'] === 'function' || ($tokens[$functionPtr]['content'] === 'fn' && self::isArrowFunction($phpcsFile, $functionPtr))) {
return null;
}
return $functionPtr;
Expand Down
22 changes: 9 additions & 13 deletions VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
use PHPCSUtils\Utils\Lists;
use PHPCSUtils\Utils\FunctionDeclarations;

class VariableAnalysisSniff implements Sniff {
/**
Expand Down Expand Up @@ -239,7 +237,7 @@ public function process(File $phpcsFile, $stackPtr) {
return;
}
if (in_array($token['code'], $scopeStartTokenTypes, true)
|| FunctionDeclarations::isArrowFunction($phpcsFile, $stackPtr)
|| Helpers::isArrowFunction($phpcsFile, $stackPtr)
) {
Helpers::debug('found scope condition', $token);
$this->recordScopeStartAndEnd($phpcsFile, $stackPtr);
Expand Down Expand Up @@ -987,13 +985,12 @@ protected function processVariableAsListShorthandAssignment(File $phpcsFile, $st
}

// OK, we're a [ ... ] construct... are we being assigned to?
try {
$assignments = Lists::getAssignments($phpcsFile, $openPtr);
} catch (\Exception $error) {
$assignments = Helpers::getListAssignments($phpcsFile, $openPtr);
if (! $assignments) {
return false;
}
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, $assignment) use ($stackPtr) {
if ($assignment === $stackPtr) {
return $assignment;
}
return $thisAssignment;
Expand Down Expand Up @@ -1030,13 +1027,12 @@ protected function processVariableAsListAssignment(File $phpcsFile, $stackPtr, $
}

// OK, we're a list (...) construct... are we being assigned to?
try {
$assignments = Lists::getAssignments($phpcsFile, $prevPtr);
} catch (\Exception $error) {
$assignments = Helpers::getListAssignments($phpcsFile, $prevPtr);
if (! $assignments) {
return false;
}
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, array $assignment) use ($stackPtr) {
if (isset($assignment['assignment_token']) && $assignment['assignment_token'] === $stackPtr) {
$matchingAssignment = array_reduce($assignments, function ($thisAssignment, $assignment) use ($stackPtr) {
if ($assignment === $stackPtr) {
return $assignment;
}
return $thisAssignment;
Expand Down
2 changes: 0 additions & 2 deletions VariableAnalysis/ruleset.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@
<ruleset name="VariableAnalysis">
<description>Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problematic variable use.</description>
<rule ref="VariableAnalysis.CodeAnalysis.VariableAnalysis"/>
<!-- PHPCSUtils is required for this sniff to operate -->
<rule ref="PHPCSUtils"/>
</ruleset>
3 changes: 1 addition & 2 deletions composer.circleci.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
},
"require" : {
"php" : ">=5.4.0",
"squizlabs/php_codesniffer": "^3.1",
"phpcsstandards/phpcsutils": "^1.0"
"squizlabs/php_codesniffer": "^3.1"
},
"require-dev": {
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.5 || ^7.0 || ^8.0"
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@
},
"require" : {
"php" : ">=5.4.0",
"squizlabs/php_codesniffer": "^3.5",
"phpcsstandards/phpcsutils": "^1.0"
"squizlabs/php_codesniffer": "^3.5"
},
"require-dev": {
"phpunit/phpunit": "^5.0 || ^6.5 || ^7.0 || ^8.0",
"sirbrillig/phpcs-import-detection": "^1.1",
"limedeck/phpunit-detailed-printer": "^3.1 || ^4.0 || ^5.0",
"phpstan/phpstan": "^0.11.8"
"phpstan/phpstan": "^0.11.8",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0"
}
}

0 comments on commit a12284e

Please sign in to comment.