Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/csqa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
# Prevent the build from running when there are only irrelevant changes.
push:
paths-ignore:
- '**.md'
- "**.md"
pull_request:
# Allow manually triggering the workflow.
workflow_dispatch:
Expand All @@ -18,11 +18,11 @@ concurrency:

jobs:
checkcs:
name: 'Basic CS and QA checks'
name: "Basic CS and QA checks"
runs-on: ubuntu-latest

env:
XMLLINT_INDENT: ' '
XMLLINT_INDENT: " "

steps:
- name: Checkout code
Expand All @@ -31,13 +31,13 @@ jobs:
- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: 'latest'
php-version: "latest"
coverage: none
tools: cs2pr

# Using PHPCS `master` as an early detection system for bugs upstream.
- name: 'Composer: adjust dependencies'
run: composer require --no-update squizlabs/php_codesniffer:"dev-master"
- name: "Composer: adjust dependencies"
run: composer require --no-update squizlabs/php_codesniffer:"4.x-dev"

# Install dependencies and handle caching in one go.
# @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
Expand Down
104 changes: 62 additions & 42 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,60 @@ jobs:
# - PHP 8.4 needs PHPCS 3.11.0+ to run without errors (though the errors don't affect this package).
#
# The matrix is set up so as not to duplicate the builds which are run for code coverage.
php: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3']
phpcs_version: ['3.5.6', 'dev-master']
php: ["5.5", "5.6", "7.0", "7.1", "7.2", "7.3"]
phpcs_version: ["3.5.7", "4.0.0", "4.x-dev"]

exclude:
# PHPCS 4.x requires PHP 7.2+
- php: "5.5"
phpcs_version: "4.0.0"
- php: "5.5"
phpcs_version: "4.x-dev"
- php: "5.6"
phpcs_version: "4.0.0"
- php: "5.6"
phpcs_version: "4.x-dev"
- php: "7.0"
phpcs_version: "4.0.0"
- php: "7.0"
phpcs_version: "4.x-dev"
- php: "7.1"
phpcs_version: "4.0.0"
- php: "7.1"
phpcs_version: "4.x-dev"

include:
# Make the matrix complete without duplicating builds run in code coverage.
- php: '8.4'
phpcs_version: '3.6.1'

- php: '8.3'
phpcs_version: 'dev-master'
- php: '8.3'
phpcs_version: '3.6.1'

- php: '8.2'
phpcs_version: 'dev-master'
- php: '8.2'
phpcs_version: '3.6.1'

- php: '8.1'
phpcs_version: 'dev-master'
- php: '8.1'
phpcs_version: '3.6.1'

- php: '8.0'
phpcs_version: 'dev-master'
- php: '8.0'
phpcs_version: '3.5.7'

- php: '7.4'
phpcs_version: 'dev-master'
- php: "8.4"
phpcs_version: "4.0.0"
- php: "8.4"
phpcs_version: "3.6.1"

- php: "8.3"
phpcs_version: "4.x-dev"
- php: "8.3"
phpcs_version: "3.6.1"

- php: "8.2"
phpcs_version: "4.x-dev"
- php: "8.2"
phpcs_version: "3.6.1"

- php: "8.1"
phpcs_version: "4.x-dev"
- php: "8.1"
phpcs_version: "3.6.1"

- php: "8.0"
phpcs_version: "4.x-dev"
- php: "8.0"
phpcs_version: "3.5.7"

- php: "7.4"
phpcs_version: "4.x-dev"

# Experimental builds.
- php: '8.5' # Nightly.
phpcs_version: 'dev-master'
- php: "8.5" # Nightly.
phpcs_version: "4.x-dev"

name: "Test: PHP ${{ matrix.php }} on PHPCS ${{ matrix.phpcs_version }}"

Expand All @@ -82,7 +102,7 @@ jobs:
run: |
# On stable PHPCS versions, allow for PHP deprecation notices.
# Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
if [ "${{ matrix.phpcs_version }}" != "dev-master" ]; then
if [ "${{ matrix.phpcs_version }}" != "4.x-dev" ]; then
echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On, zend.assertions=1' >> $GITHUB_OUTPUT
else
echo 'PHP_INI=error_reporting=-1, display_errors=On, zend.assertions=1' >> $GITHUB_OUTPUT
Expand All @@ -95,7 +115,7 @@ jobs:
ini-values: ${{ steps.set_ini.outputs.PHP_INI }}
coverage: none

- name: 'Composer: adjust dependencies'
- name: "Composer: adjust dependencies"
run: |
# Remove dev dependencies which are not compatible with all supported PHP versions.
composer remove --dev --no-update sirbrillig/phpcs-import-detection phpstan/phpstan
Expand Down Expand Up @@ -146,15 +166,15 @@ jobs:
strategy:
matrix:
include:
- php: '8.4'
phpcs_version: 'dev-master'
- php: '7.4'
phpcs_version: '3.5.6'
- php: "8.4"
phpcs_version: "4.x-dev"
- php: "7.4"
phpcs_version: "3.5.7"

- php: '5.4'
phpcs_version: 'dev-master'
- php: '5.4'
phpcs_version: '3.5.6'
- php: "7.2"
phpcs_version: "4.x-dev"
- php: "5.4"
phpcs_version: "3.5.7"

name: "Coverage: PHP ${{ matrix.php }} on PHPCS ${{ matrix.phpcs_version }}"

Expand All @@ -167,7 +187,7 @@ jobs:
run: |
# On stable PHPCS versions, allow for PHP deprecation notices.
# Unit tests don't need to fail on those for stable releases where those issues won't get fixed anymore.
if [ "${{ matrix.phpcs_version }}" != "dev-master" ]; then
if [ "${{ matrix.phpcs_version }}" != "4.x-dev" ]; then
echo 'PHP_INI=error_reporting=E_ALL & ~E_DEPRECATED, display_errors=On, zend.assertions=1' >> $GITHUB_OUTPUT
else
echo 'PHP_INI=error_reporting=-1, display_errors=On, zend.assertions=1' >> $GITHUB_OUTPUT
Expand All @@ -180,7 +200,7 @@ jobs:
ini-values: ${{ steps.set_ini.outputs.PHP_INI }}
coverage: xdebug

- name: 'Composer: adjust dependencies'
- name: "Composer: adjust dependencies"
run: |
# Remove dev dependencies which are not compatible with all supported PHP/PHPCS versions.
composer remove --dev --no-update phpcsstandards/phpcsdevcs sirbrillig/phpcs-import-detection phpstan/phpstan
Expand Down
74 changes: 47 additions & 27 deletions VariableAnalysis/Lib/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public static function findContainingOpeningBracket(File $phpcsFile, $stackPtr)
$tokens = $phpcsFile->getTokens();
if (isset($tokens[$stackPtr]['nested_parenthesis'])) {
/**
* @var array<int|string|null>
* @var list<int|string>
*/
$openPtrs = array_keys($tokens[$stackPtr]['nested_parenthesis']);
return (int)end($openPtrs);
Expand Down Expand Up @@ -319,8 +319,18 @@ public static function findFunctionCall(File $phpcsFile, $stackPtr)
if (is_int($openPtr)) {
// First non-whitespace thing and see if it's a T_STRING function name
$functionPtr = $phpcsFile->findPrevious(Tokens::$emptyTokens, $openPtr - 1, null, true, null, true);
if (is_int($functionPtr) && $tokens[$functionPtr]['code'] === T_STRING) {
return $functionPtr;
if (is_int($functionPtr)) {
$functionTokenCode = $tokens[$functionPtr]['code'];
// In PHPCS 4.x, function names can be T_NAME_FULLY_QUALIFIED, T_NAME_QUALIFIED, or T_NAME_RELATIVE
$validFunctionTokens = [
T_STRING,
T_NAME_FULLY_QUALIFIED,
T_NAME_QUALIFIED,
T_NAME_RELATIVE,
];
if (in_array($functionTokenCode, $validFunctionTokens, true)) {
return $functionPtr;
}
}
}
return null;
Expand Down Expand Up @@ -364,9 +374,6 @@ public static function findFunctionCallArguments(File $phpcsFile, $stackPtr)
if (self::findContainingOpeningBracket($phpcsFile, $nextPtr) === $openPtr) {
// Comma is at our level of brackets, it's an argument delimiter.
$range = range($lastArgComma + 1, $nextPtr - 1);
$range = array_filter($range, function ($element) {
return is_int($element);
});
array_push($argPtrs, $range);
$lastArgComma = $nextPtr;
}
Expand Down Expand Up @@ -394,7 +401,8 @@ public static function getNextAssignPointer(File $phpcsFile, $stackPtr)

// Is the next non-whitespace an assignment?
$nextPtr = $phpcsFile->findNext(Tokens::$emptyTokens, $stackPtr + 1, null, true, null, true);
if (is_int($nextPtr)
if (
is_int($nextPtr)
&& isset(Tokens::$assignmentTokens[$tokens[$nextPtr]['code']])
// Ignore double arrow to prevent triggering on `foreach ( $array as $k => $v )`.
&& $tokens[$nextPtr]['code'] !== T_DOUBLE_ARROW
Expand Down Expand Up @@ -548,6 +556,9 @@ public static function findVariableScopeExceptArrowFunctions(File $phpcsFile, $s
T_DOUBLE_QUOTED_STRING,
T_HEREDOC,
T_STRING,
T_NAME_FULLY_QUALIFIED,
T_NAME_QUALIFIED,
T_NAME_RELATIVE,
];
if (! in_array($tokens[$stackPtr]['code'], $allowedTypes, true)) {
throw new \Exception("Cannot find variable scope for non-variable {$tokens[$stackPtr]['type']}");
Expand Down Expand Up @@ -618,24 +629,6 @@ private static function getStartOfTokenScope(File $phpcsFile, $stackPtr)
return 0;
}

/**
* @param File $phpcsFile
* @param int $stackPtr
*
* @return bool
*/
public static function isTokenInsideArrowFunctionDefinition(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$token = $tokens[$stackPtr];
$openParenIndices = isset($token['nested_parenthesis']) ? $token['nested_parenthesis'] : [];
if (empty($openParenIndices)) {
return false;
}
$openParenPtr = $openParenIndices[0];
return self::isArrowFunction($phpcsFile, $openParenPtr - 1);
}

/**
* @param File $phpcsFile
* @param int $stackPtr
Expand Down Expand Up @@ -1290,7 +1283,7 @@ public static function getFunctionIndexForFunctionCallArgument(File $phpcsFile,
return null;
}
/**
* @var array<int|string|null>
* @var list<int|string>
*/
$startingParenthesis = array_keys($token['nested_parenthesis']);
$startOfArguments = end($startingParenthesis);
Expand Down Expand Up @@ -1681,9 +1674,27 @@ public static function getFunctionNameWithNamespace(File $phpcsFile, $stackPtr)
$startOfScope = self::findVariableScope($phpcsFile, $stackPtr);
$functionName = $tokens[$stackPtr]['content'];

// In PHPCS 4.x, T_NAME_FULLY_QUALIFIED, T_NAME_QUALIFIED, and T_NAME_RELATIVE
// tokens already contain the full namespaced name, so we can return early.
if ($tokens[$stackPtr]['code'] === T_NAME_FULLY_QUALIFIED) {
return $functionName;
}
if ($tokens[$stackPtr]['code'] === T_NAME_QUALIFIED) {
return $functionName;
}
if ($tokens[$stackPtr]['code'] === T_NAME_RELATIVE) {
return $functionName;
}

// Move backwards from the token, collecting namespace separators and
// strings, until we encounter whitespace or something else.
$partOfNamespace = [T_NS_SEPARATOR, T_STRING];
$partOfNamespace = [
T_NS_SEPARATOR,
T_STRING,
T_NAME_QUALIFIED,
T_NAME_RELATIVE,
T_NAME_FULLY_QUALIFIED,
];
for ($i = $stackPtr - 1; $i > $startOfScope; $i--) {
if (! in_array($tokens[$i]['code'], $partOfNamespace, true)) {
break;
Expand All @@ -1708,6 +1719,15 @@ private static function isTokenPossiblyPartOfTypehint(File $phpcsFile, $stackPtr
if ($token['code'] === 'PHPCS_T_NULLABLE') {
return true;
}
if ($token['code'] === T_NAME_QUALIFIED) {
return true;
}
if ($token['code'] === T_NAME_RELATIVE) {
return true;
}
if ($token['code'] === T_NAME_FULLY_QUALIFIED) {
return true;
}
if ($token['code'] === T_NS_SEPARATOR) {
return true;
}
Expand Down
29 changes: 28 additions & 1 deletion VariableAnalysis/Sniffs/CodeAnalysis/VariableAnalysisSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,34 @@ protected function processVariableAsPassByReferenceFunctionCall(File $phpcsFile,

// Is our function a known pass-by-reference function?
$functionName = $tokens[$functionPtr]['content'];
$refArgs = $this->getPassByReferenceFunction($functionName);

// In PHPCS 4.x, T_NAME_FULLY_QUALIFIED, T_NAME_QUALIFIED, and T_NAME_RELATIVE
// tokens contain the full namespaced name. Extract just the base name for the
// first check so that 'my_function' in the config can match '\My\Namespace\my_function'.
$functionBaseName = $functionName;
if ($tokens[$functionPtr]['code'] === T_NAME_FULLY_QUALIFIED) {
$lastBackslashPos = strrpos($functionName, '\\');
if ($lastBackslashPos !== false) {
$functionBaseName = substr($functionName, $lastBackslashPos + 1);
}
} elseif ($tokens[$functionPtr]['code'] === T_NAME_QUALIFIED) {
$lastBackslashPos = strrpos($functionName, '\\');
if ($lastBackslashPos !== false) {
$functionBaseName = substr($functionName, $lastBackslashPos + 1);
}
} elseif ($tokens[$functionPtr]['code'] === T_NAME_RELATIVE) {
$lastBackslashPos = strrpos($functionName, '\\');
if ($lastBackslashPos !== false) {
$functionBaseName = substr($functionName, $lastBackslashPos + 1);
}
}

// Ensure we have a string (should always be true, but helps static analyzers).
if (! is_string($functionBaseName) || $functionBaseName === '') {
return false;
}

$refArgs = $this->getPassByReferenceFunction($functionBaseName);
if (! $refArgs) {
// Check again with the fully namespaced function name.
$functionName = Helpers::getFunctionNameWithNamespace($phpcsFile, $functionPtr);
Expand Down
7 changes: 3 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,12 @@
},
"require": {
"php": ">=5.4.0",
"squizlabs/php_codesniffer": "^3.5.6"
"squizlabs/php_codesniffer": "^3.5.7 || ^4.0.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3",
"phpcsstandards/phpcsdevcs": "^1.1",
"phpstan/phpstan": "^1.7",
"phpstan/phpstan": "^1.7 || ^2.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
"vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0"
"vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0 || ^6.0 || ^7.0"
}
}
Loading