clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
BSD Licence Agreement
-----------------------------------------------------------------------
This software is available to you under the BSD license,
available in the LICENSE file accompanying this software.
You may obtain a copy of the License at
http://www.opensource.org/licenses/bsd-license.php
-----------------------------------------------------------------------
Copyright (c) 2011, Sam Graham <php-codesniffer-variableanalysis BLAHBLAH illusori.co.uk>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------------------------------
Portions of this sofware derived from work Copyright (c) 2010, Monotek d.o.o,
released under a BSD License available at:
http://www.opensource.org/licenses/bsd-license.php
-----------------------------------------------------------------------
Portions of this software derived from work Copyright (c), 2006 Squiz
Pty Ltd (ABN 77 084 670 600), available under the following license:
BSD Licence Agreement
-----------------------------------------------------------------------
This software is available to you under the BSD license,
available in the LICENSE file accompanying this software.
You may obtain a copy of the License at
http://matrix.squiz.net/developer/tools/php_cs/licence
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c), 2006 Squiz Pty Ltd (ABN 77 084 670 600).
All rights reserved.

View File

@@ -0,0 +1,123 @@
# PHP_CodeSniffer VariableAnalysis
[![CS and QA Build Status](https://github.com/sirbrillig/phpcs-variable-analysis/actions/workflows/csqa.yml/badge.svg)](https://github.com/sirbrillig/phpcs-variable-analysis/actions/workflows/csqa.yml)
[![Test Build Status](https://github.com/sirbrillig/phpcs-variable-analysis/actions/workflows/test.yml/badge.svg)](https://github.com/sirbrillig/phpcs-variable-analysis/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/sirbrillig/phpcs-variable-analysis/badge.svg)](https://coveralls.io/github/sirbrillig/phpcs-variable-analysis)
Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problematic variable use.
- Warns if variables are used without being defined. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable`)
- Warns if variables are used inside `unset()` without being defined. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedUnsetVariable`)
- Warns if variables are set or declared but never used. (Sniff code: `VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable`)
- Warns if `$this`, `self::$static_member`, `static::$static_member` is used outside class scope. (Sniff codes: `VariableAnalysis.CodeAnalysis.VariableAnalysis.SelfOutsideClass` or `VariableAnalysis.CodeAnalysis.VariableAnalysis.StaticOutsideClass`)
## Installation
### Requirements
VariableAnalysis requires PHP 5.4 or higher and [PHP CodeSniffer](https://github.com/PHPCSStandards/PHP_CodeSniffer) version 3.5.6 or higher.
### With PHPCS Composer Installer
This is the easiest method.
First, install [phpcodesniffer-composer-installer](https://github.com/PHPCSStandards/composer-installer) for your project if you have not already. This will also install PHPCS.
```
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev dealerdirect/phpcodesniffer-composer-installer
```
Then install these standards.
```
composer require --dev sirbrillig/phpcs-variable-analysis
```
You can then include the sniffs by adding a line like the following to [your phpcs.xml file](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Advanced-Usage#using-a-default-configuration-file).
```
<rule ref="VariableAnalysis"/>
```
It should just work after that!
### Standalone
1. Install PHP_CodeSniffer (PHPCS) by following its [installation instructions](https://github.com/PHPCSStandards/PHP_CodeSniffer#installation) (via Composer, Phar file, PEAR, or Git checkout).
Do ensure that PHP_CodeSniffer's version matches our [requirements](#requirements).
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.
3. Add the paths of the newly installed standards to the [PHP_CodeSniffer installed_paths configuration](https://github.com/PHPCSStandards/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/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/phpcs-variable-analysis
## Customization
There's a variety of options to customize the behaviour of VariableAnalysis, take a look at the included ruleset.xml.example for commented examples of a configuration.
The available options are as follows:
- `allowUnusedFunctionParameters` (bool, default `false`): if set to true, function arguments will never be marked as unused.
- `allowUnusedCaughtExceptions` (bool, default `true`): if set to true, caught Exception variables will never be marked as unused.
- `allowUnusedParametersBeforeUsed` (bool, default `true`): if set to true, unused function arguments will be ignored if they are followed by used function arguments.
- `allowUnusedVariablesBeforeRequire` (bool, default `false`): if set to true, variables defined before a `require`, `require_once`, `include`, or `include_once` will not be marked as unused. They may be intended for the required file.
- `allowUndefinedVariablesInFileScope` (bool, default `false`): if set to true, undefined variables in the file's top-level scope will never be marked as undefined. This can be useful for template files which use many global variables defined elsewhere.
- `allowUnusedVariablesInFileScope` (bool, default `false`): if set to true, unused variables in the file's top-level scope will never be marked as unused. This can be helpful when defining a lot of global variables to be used elsewhere.
- `validUnusedVariableNames` (string, default `null`): a space-separated list of names of placeholder variables that you want to ignore from unused variable warnings. For example, to ignore the variables `$junk` and `$unused`, this could be set to `'junk unused'`.
- `ignoreUnusedRegexp` (string, default `null`): a PHP regexp string (note that this requires explicit delimiters) for variables that you want to ignore from unused variable warnings. For example, to ignore the variables `$_junk` and `$_unused`, this could be set to `'/^_/'`.
- `validUndefinedVariableNames` (string, default `null`): a space-separated list of names of placeholder variables that you want to ignore from undefined variable warnings. For example, to ignore the variables `$post` and `$undefined`, this could be set to `'post undefined'`. This can be used in combination with `validUndefinedVariableRegexp`.
- `validUndefinedVariableRegexp` (string, default `null`): a PHP regexp string (note that this requires explicit delimiters) for variables that you want to ignore from undefined variable warnings. For example, to ignore the variables `$post` and `$undefined`, this could be set to `'/^(post|undefined)$/'`. This can be used in combination with `validUndefinedVariableNames`.
- `allowUnusedForeachVariables` (bool, default `true`): if set to true, unused values from the `key => value` syntax in a `foreach` loop will never be marked as unused.
- `sitePassByRefFunctions` (string, default `null`): a list of custom functions which pass in variables to be initialized by reference (eg `preg_match()`) and therefore should not require those variables to be defined ahead of time. The list is space separated and each entry is of the form `functionName:1,2`. The function name comes first followed by a colon and a comma-separated list of argument numbers (starting from 1) which should be considered variable definitions. The special value `...` in the arguments list will cause all arguments after the last number to be considered variable definitions.
- `allowWordPressPassByRefFunctions` (bool, default `false`): if set to true, a list of common WordPress pass-by-reference functions will be added to the list of PHP ones so that passing undefined variables to these functions (to be initialized by reference) will be allowed.
To set these these options, you must use XML in your ruleset. For details, see the [phpcs customizable sniff properties page](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties). Here is an example that ignores all variables that start with an underscore:
```xml
<rule ref="VariableAnalysis.CodeAnalysis.VariableAnalysis">
<properties>
<property name="ignoreUnusedRegexp" value="/^_/"/>
</properties>
</rule>
```
## See Also
- [ImportDetection](https://github.com/sirbrillig/phpcs-import-detection): A set of phpcs sniffs to look for unused or unimported symbols.
- [phpcs-changed](https://github.com/sirbrillig/phpcs-changed): Run phpcs on files, but only report warnings/errors from lines which were changed.
## Original
This was forked from the excellent work in https://github.com/illusori/PHP_Codesniffer-VariableAnalysis
## Contributing
Please open issues or PRs on this repository.
Any changes should be accompanied by tests and should pass linting and static analysis. Please use phpdoc (rather than actual types) for declaring types since this must run in PHP 5.4.
To run tests, make sure composer is installed, then run:
```
composer install # you only need to do this once
composer test
```
To run linting, use:
```
composer lint
```
To run static analysis, use:
```
composer phpstan
```

View File

@@ -0,0 +1,260 @@
<?php
namespace VariableAnalysis\Lib;
class Constants
{
/**
* Array of known pass-by-reference functions and the argument(s) which are passed
* by reference, the arguments are numbered starting from 1 and an elipsis '...'
* means all argument numbers after the previous should be considered pass-by-reference.
*
* This does not need to cover all pass-by-reference arguments, only the
* ones which can be passed an undefined variable (eg: `$matches` in
* `preg_match`) and will define that variable.
*
* @return array<string, array<int|string>>
*/
public static function getPassByReferenceFunctions()
{
return [
'__soapCall' => [5],
'addFunction' => [3],
'addTask' => [3],
'addTaskBackground' => [3],
'addTaskHigh' => [3],
'addTaskHighBackground' => [3],
'addTaskLow' => [3],
'addTaskLowBackground' => [3],
'addTaskStatus' => [2],
'apc_dec' => [3],
'apc_fetch' => [2],
'apc_inc' => [3],
'apcu_dec' => [3],
'apcu_fetch' => [2],
'apcu_inc' => [3],
'areConfusable' => [3],
'arsort' => [1],
'asort' => [1],
'bindColumn' => [2],
'bindParam' => [2],
'bind_param' => [2, 3, '...'],
'bind_result' => [1, 2, '...'],
'call_user_method' => [2],
'call_user_method_array' => [2],
'curl_multi_exec' => [2],
'curl_multi_info_read' => [2],
'current' => [1],
'dbplus_curr' => [2],
'dbplus_first' => [2],
'dbplus_info' => [3],
'dbplus_last' => [2],
'dbplus_next' => [2],
'dbplus_prev' => [2],
'dbplus_tremove' => [3],
'dns_get_record' => [3, 4],
'domxml_open_file' => [3],
'domxml_open_mem' => [3],
'each' => [1],
'enchant_dict_quick_check' => [3],
'end' => [1],
'ereg' => [3],
'eregi' => [3],
'exec' => [2, 3],
'exif_thumbnail' => [1, 2, 3],
'expect_expectl' => [3],
'extract' => [1],
'filter' => [3],
'flock' => [2,3],
'fscanf' => [2, 3, '...'],
'fsockopen' => [3, 4],
'ftp_alloc' => [3],
'get' => [2, 3],
'getByKey' => [4],
'getMulti' => [2],
'getMultiByKey' => [3],
'getimagesize' => [2],
'getmxrr' => [2, 3],
'gnupg_decryptverify' => [3],
'gnupg_verify' => [4],
'grapheme_extract' => [5],
'headers_sent' => [1, 2],
'http_build_url' => [4],
'http_get' => [3],
'http_head' => [3],
'http_negotiate_charset' => [2],
'http_negotiate_content_type' => [2],
'http_negotiate_language' => [2],
'http_post_data' => [4],
'http_post_fields' => [5],
'http_put_data' => [4],
'http_put_file' => [4],
'http_put_stream' => [4],
'http_request' => [5],
'isSuspicious' => [2],
'is_callable' => [3],
'key' => [1],
'krsort' => [1],
'ksort' => [1],
'ldap_get_option' => [3],
'ldap_parse_reference' => [3],
'ldap_parse_result' => [3, 4, 5, 6],
'localtime' => [2],
'm_completeauthorizations' => [2],
'maxdb_stmt_bind_param' => [3, 4, '...'],
'maxdb_stmt_bind_result' => [2, 3, '...'],
'mb_convert_variables' => [3, 4, '...'],
'mb_parse_str' => [2],
'mqseries_back' => [2, 3],
'mqseries_begin' => [3, 4],
'mqseries_close' => [4, 5],
'mqseries_cmit' => [2, 3],
'mqseries_conn' => [2, 3, 4],
'mqseries_connx' => [2, 3, 4, 5],
'mqseries_disc' => [2, 3],
'mqseries_get' => [3, 4, 5, 6, 7, 8, 9],
'mqseries_inq' => [6, 8, 9, 10],
'mqseries_open' => [2, 4, 5, 6],
'mqseries_put' => [3, 4, 6, 7],
'mqseries_put1' => [2, 3, 4, 6, 7],
'mqseries_set' => [9, 10],
'msg_receive' => [3, 5, 8],
'msg_send' => [6],
'mssql_bind' => [3],
'natcasesort' => [1],
'natsort' => [1],
'ncurses_color_content' => [2, 3, 4],
'ncurses_getmaxyx' => [2, 3],
'ncurses_getmouse' => [1],
'ncurses_getyx' => [2, 3],
'ncurses_instr' => [1],
'ncurses_mouse_trafo' => [1, 2],
'ncurses_mousemask' => [2],
'ncurses_pair_content' => [2, 3],
'ncurses_wmouse_trafo' => [2, 3],
'newt_button_bar' => [1],
'newt_form_run' => [2],
'newt_get_screen_size' => [1, 2],
'newt_grid_get_size' => [2, 3],
'newt_reflow_text' => [5, 6],
'newt_win_entries' => [7],
'newt_win_menu' => [8],
'next' => [1],
'oci_bind_array_by_name' => [3],
'oci_bind_by_name' => [3],
'oci_define_by_name' => [3],
'oci_fetch_all' => [2],
'ocifetchinto' => [2],
'odbc_fetch_into' => [2],
'openssl_csr_export' => [2],
'openssl_csr_new' => [2],
'openssl_open' => [2],
'openssl_pkcs12_export' => [2],
'openssl_pkcs12_read' => [2],
'openssl_pkey_export' => [2],
'openssl_private_decrypt' => [2],
'openssl_private_encrypt' => [2],
'openssl_public_decrypt' => [2],
'openssl_public_encrypt' => [2],
'openssl_random_pseudo_bytes' => [2],
'openssl_seal' => [2, 3],
'openssl_sign' => [2],
'openssl_x509_export' => [2],
'ovrimos_fetch_into' => [2],
'parse' => [2,3],
'parseCurrency' => [2, 3],
'parse_str' => [2],
'parsekit_compile_file' => [2],
'parsekit_compile_string' => [2],
'passthru' => [2],
'pcntl_sigprocmask' => [3],
'pcntl_sigtimedwait' => [2],
'pcntl_sigwaitinfo' => [2],
'pcntl_wait' => [1],
'pcntl_waitpid' => [2],
'pfsockopen' => [3, 4],
'php_check_syntax' => [2],
'poll' => [1, 2, 3],
'preg_filter' => [5],
'preg_match' => [3],
'preg_match_all' => [3],
'preg_replace' => [5],
'preg_replace_callback' => [5],
'prev' => [1],
'proc_open' => [3],
'query' => [3],
'queryExec' => [2],
'reset' => [1],
'rsort' => [1],
'settype' => [1],
'shuffle' => [1],
'similar_text' => [3],
'socket_create_pair' => [4],
'socket_getpeername' => [2, 3],
'socket_getsockname' => [2, 3],
'socket_recv' => [2],
'socket_recvfrom' => [2, 5, 6],
'socket_select' => [1, 2, 3],
'sort' => [1],
'sortWithSortKeys' => [1],
'sqlite_exec' => [3],
'sqlite_factory' => [3],
'sqlite_open' => [3],
'sqlite_popen' => [3],
'sqlite_query' => [4],
'sqlite_unbuffered_query' => [4],
'sscanf' => [3, '...'],
'str_ireplace' => [4],
'str_replace' => [4],
'stream_open' => [4],
'stream_select' => [1, 2, 3],
'stream_socket_accept' => [3],
'stream_socket_client' => [2, 3],
'stream_socket_recvfrom' => [4],
'stream_socket_server' => [2, 3],
'system' => [2],
'uasort' => [1],
'uksort' => [1],
'unbufferedQuery' => [3],
'usort' => [1],
'wincache_ucache_dec' => [3],
'wincache_ucache_get' => [2],
'wincache_ucache_inc' => [3],
'xdiff_string_merge3' => [4],
'xdiff_string_patch' => [4],
'xml_parse_into_struct' => [3, 4],
'xml_set_object' => [2],
'xmlrpc_decode_request' => [2],
'xmlrpc_set_type' => [1],
'xslt_set_object' => [2],
'yaml_parse' => [3],
'yaml_parse_file' => [3],
'yaml_parse_url' => [3],
'yaz_ccl_parse' => [3],
'yaz_hits' => [2],
'yaz_scan_result' => [2],
'yaz_wait' => [1],
];
}
/**
* @return array<string, array<int>>
*/
public static function getWordPressPassByReferenceFunctions()
{
return [
'wp_parse_str' => [2],
'wp_cache_get' => [4],
];
}
/**
* A regexp for matching variable names in double-quoted strings.
*
* @return string
*/
public static function getDoubleQuotedVarRegexp()
{
return '|(?<!\\\\)(?:\\\\{2})*\${?([a-zA-Z0-9_]+)}?|';
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace VariableAnalysis\Lib;
/**
* Holds details of an enum.
*/
class EnumInfo
{
/**
* The position of the `enum` token.
*
* @var int
*/
public $enumIndex;
/**
* The position of the block opener (curly brace) for the enum.
*
* @var int
*/
public $blockStart;
/**
* The position of the block closer (curly brace) for the enum.
*
* @var int
*/
public $blockEnd;
/**
* @param int $enumIndex
* @param int $blockStart
* @param int $blockEnd
*/
public function __construct(
$enumIndex,
$blockStart,
$blockEnd
) {
$this->enumIndex = $enumIndex;
$this->blockStart = $blockStart;
$this->blockEnd = $blockEnd;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace VariableAnalysis\Lib;
/**
* Holds details of a for loop.
*/
class ForLoopInfo
{
/**
* The position of the `for` token.
*
* @var int
*/
public $forIndex;
/**
* The position of the initialization expression opener for the loop.
*
* @var int
*/
public $initStart;
/**
* The position of the initialization expression closer for the loop.
*
* @var int
*/
public $initEnd;
/**
* The position of the condition expression opener for the loop.
*
* @var int
*/
public $conditionStart;
/**
* The position of the condition expression closer for the loop.
*
* @var int
*/
public $conditionEnd;
/**
* The position of the increment expression opener for the loop.
*
* @var int
*/
public $incrementStart;
/**
* The position of the increment expression closer for the loop.
*
* @var int
*/
public $incrementEnd;
/**
* The position of the block opener for the loop.
*
* @var int
*/
public $blockStart;
/**
* The position of the block closer for the loop.
*
* @var int
*/
public $blockEnd;
/**
* Any variables defined inside the third expression of the loop.
*
* The key is the variable index.
*
* @var array<int, \VariableAnalysis\Lib\VariableInfo>
*/
public $incrementVariables = [];
/**
* @param int $forIndex
* @param int $blockStart
* @param int $blockEnd
* @param int $initStart
* @param int $initEnd
* @param int $conditionStart
* @param int $conditionEnd
* @param int $incrementStart
* @param int $incrementEnd
*/
public function __construct(
$forIndex,
$blockStart,
$blockEnd,
$initStart,
$initEnd,
$conditionStart,
$conditionEnd,
$incrementStart,
$incrementEnd
) {
$this->forIndex = $forIndex;
$this->blockStart = $blockStart;
$this->blockEnd = $blockEnd;
$this->initStart = $initStart;
$this->initEnd = $initEnd;
$this->conditionStart = $conditionStart;
$this->conditionEnd = $conditionEnd;
$this->incrementStart = $incrementStart;
$this->incrementEnd = $incrementEnd;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
<?php
namespace VariableAnalysis\Lib;
/**
* Holds details of a scope.
*/
class ScopeInfo
{
/**
* The token index of the start of this scope.
*
* @var int
*/
public $scopeStartIndex;
/**
* The token index of the end of this scope, if important.
*
* @var int|null
*/
public $scopeEndIndex;
/**
* The variables defined in this scope.
*
* @var VariableInfo[]
*/
public $variables = [];
/**
* @param int $scopeStartIndex
* @param int|null $scopeEndIndex
*/
public function __construct($scopeStartIndex, $scopeEndIndex = null)
{
$this->scopeStartIndex = $scopeStartIndex;
$this->scopeEndIndex = $scopeEndIndex;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace VariableAnalysis\Lib;
use VariableAnalysis\Lib\ScopeInfo;
use VariableAnalysis\Lib\Helpers;
use PHP_CodeSniffer\Files\File;
class ScopeManager
{
/**
* An associative array of a list of token index pairs which start and end
* scopes and will be used to check for unused variables.
*
* The outer array of scopes is keyed by a string containing the filename.
* The inner array of scopes in keyed by the scope start token index.
*
* @var array<string, array<int, ScopeInfo>>
*/
private $scopes = [];
/**
* Add a scope's start and end index to our record for the file.
*
* @param File $phpcsFile
* @param int $scopeStartIndex
*
* @return ScopeInfo
*/
public function recordScopeStartAndEnd(File $phpcsFile, $scopeStartIndex)
{
$scopeEndIndex = Helpers::getScopeCloseForScopeOpen($phpcsFile, $scopeStartIndex);
$filename = $phpcsFile->getFilename();
if (! isset($this->scopes[$filename])) {
$this->scopes[$filename] = [];
}
Helpers::debug('recording scope for file', $filename, 'start/end', $scopeStartIndex, $scopeEndIndex);
$scope = new ScopeInfo($scopeStartIndex, $scopeEndIndex);
$this->scopes[$filename][$scopeStartIndex] = $scope;
return $scope;
}
/**
* Return the scopes for a file.
*
* @param string $filename
*
* @return ScopeInfo[]
*/
public function getScopesForFilename($filename)
{
if (empty($this->scopes[$filename])) {
return [];
}
return array_values($this->scopes[$filename]);
}
/**
* Return the scope for a scope start index.
*
* @param string $filename
* @param int $scopeStartIndex
*
* @return ScopeInfo|null
*/
public function getScopeForScopeStart($filename, $scopeStartIndex)
{
if (empty($this->scopes[$filename][$scopeStartIndex])) {
return null;
}
return $this->scopes[$filename][$scopeStartIndex];
}
/**
* Find scopes closed by a scope close index.
*
* @param string $filename
* @param int $scopeEndIndex
*
* @return ScopeInfo[]
*/
public function getScopesForScopeEnd($filename, $scopeEndIndex)
{
$scopePairsForFile = $this->getScopesForFilename($filename);
$scopeIndicesThisCloses = array_reduce(
$scopePairsForFile,
/**
* @param ScopeInfo[] $found
* @param ScopeInfo $scope
*
* @return ScopeInfo[]
*/
function ($found, $scope) use ($scopeEndIndex) {
if (! is_int($scope->scopeEndIndex)) {
Helpers::debug('No scope closer found for scope start', $scope->scopeStartIndex);
return $found;
}
if ($scopeEndIndex === $scope->scopeEndIndex) {
$found[] = $scope;
}
return $found;
},
[]
);
return $scopeIndicesThisCloses;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace VariableAnalysis\Lib;
class ScopeType
{
const PARAM = 'param';
const BOUND = 'bound';
const LOCAL = 'local';
const GLOBALSCOPE = 'global';
const STATICSCOPE = 'static';
}

View File

@@ -0,0 +1,109 @@
<?php
namespace VariableAnalysis\Lib;
use VariableAnalysis\Lib\ScopeType;
/**
* Holds details of a variable within a scope.
*/
class VariableInfo
{
/**
* @var string
*/
public $name;
/**
* What scope the variable has: local, param, static, global, bound
*
* @var ScopeType::PARAM|ScopeType::BOUND|ScopeType::LOCAL|ScopeType::GLOBALSCOPE|ScopeType::STATICSCOPE|null
*/
public $scopeType;
/**
* @var string|null
*/
public $typeHint;
/**
* @var int|null
*/
public $referencedVariableScope;
/**
* True if the variable is a reference but one created at runtime
*
* @var bool
*/
public $isDynamicReference = false;
/**
* Stack pointer of first declaration
*
* Declaration is when a variable is created but has no value assigned.
*
* Assignment by reference is also a declaration and not an initialization.
*
* @var int|null
*/
public $firstDeclared;
/**
* Stack pointer of first initialization
*
* @var int|null
*/
public $firstInitialized;
/**
* Stack pointer of first read
*
* @var int|null
*/
public $firstRead;
/**
* Stack pointers of all assignments
*
* This includes both declarations and initializations and may contain
* duplicates!
*
* @var int[]
*/
public $allAssignments = [];
/**
* @var bool
*/
public $ignoreUnused = false;
/**
* @var bool
*/
public $ignoreUndefined = false;
/**
* @var bool
*/
public $isForeachLoopAssociativeValue = false;
/**
* @var array<ScopeType::PARAM|ScopeType::BOUND|ScopeType::LOCAL|ScopeType::GLOBALSCOPE|ScopeType::STATICSCOPE, string>
*/
public static $scopeTypeDescriptions = [
ScopeType::LOCAL => 'variable',
ScopeType::PARAM => 'function parameter',
ScopeType::STATICSCOPE => 'static variable',
ScopeType::GLOBALSCOPE => 'global variable',
ScopeType::BOUND => 'bound variable',
];
/**
* @param string $varName
*/
public function __construct($varName)
{
$this->name = $varName;
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="VariableAnalysis" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/phpcsstandards/PHP_CodeSniffer/master/phpcs.xsd">
<description>Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problematic variable use.</description>
</ruleset>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<ruleset name="VariableAnalysis">
<description>Plugin for PHP_CodeSniffer static analysis tool that adds analysis of problematic variable use.</description>
<!-- If you want everything, with the defaults, just include this line -->
<rule ref="VariableAnalysis"/>
<!--
You can also refer to specific sniff codes to customize or disable them. See the README for a list of the available codes.
There are also many options that can be set to change the behavior of this sniff. See the "Customization" section of the README.
-->
</ruleset>

View File

@@ -0,0 +1,64 @@
{
"name": "sirbrillig/phpcs-variable-analysis",
"description": "A PHPCS sniff to detect problems with variables.",
"type": "phpcodesniffer-standard",
"keywords" : [ "phpcs", "static analysis" ],
"license": "BSD-2-Clause",
"authors": [
{
"name": "Sam Graham",
"email": "php-codesniffer-variableanalysis@illusori.co.uk"
},
{
"name": "Payton Swick",
"email": "payton@foolord.com"
}
],
"support" : {
"issues": "https://github.com/sirbrillig/phpcs-variable-analysis/issues",
"wiki" : "https://github.com/sirbrillig/phpcs-variable-analysis/wiki",
"source": "https://github.com/sirbrillig/phpcs-variable-analysis"
},
"config": {
"sort-order": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
},
"lock": false
},
"autoload": {
"psr-4": {
"VariableAnalysis\\": "VariableAnalysis/"
}
},
"autoload-dev": {
"psr-4": {
"VariableAnalysis\\Tests\\": "Tests/"
}
},
"minimum-stability": "dev",
"prefer-stable": true,
"scripts": {
"test": "./vendor/bin/phpunit --no-coverage",
"coverage": "./vendor/bin/phpunit",
"test-lte9": "./vendor/bin/phpunit -c phpunitlte9.xml.dist --no-coverage",
"coverage-lte9": "./vendor/bin/phpunit -c phpunitlte9.xml.dist",
"lint": "./vendor/bin/phpcs",
"fix": "./vendor/bin/phpcbf",
"phpstan": "./vendor/bin/phpstan analyse",
"psalm": "./vendor/bin/psalm --no-cache",
"static-analysis": "composer phpstan && composer psalm"
},
"require" : {
"php" : ">=5.4.0",
"squizlabs/php_codesniffer": "^3.5.6"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.5 || ^7.0 || ^8.0 || ^9.0 || ^10.5.32 || ^11.3.3",
"sirbrillig/phpcs-import-detection": "^1.1",
"phpcsstandards/phpcsdevcs": "^1.1",
"phpstan/phpstan": "^1.7",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0",
"vimeo/psalm": "^0.2 || ^0.3 || ^1.1 || ^4.24 || ^5.0"
}
}

View File

@@ -0,0 +1,4 @@
<?php
require_once __DIR__ . '/vendor/squizlabs/php_codesniffer/src/Util/Tokens.php';
require_once __DIR__ . '/vendor/autoload.php';

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<psalm
errorLevel="3"
resolveFromConfigFile="true"
autoloader="psalm-autoloader.php"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="VariableAnalysis" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="vendor/squizlabs/php_codesniffer" />
</extraFiles>
</psalm>