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,135 @@
<?php
/**
* \Drupal\Sniffs\Semantics\ConstantNameSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that constants introduced with define() in module or install files start
* with the module's name.
*
* Largely copied from
* \PHP_CodeSniffer\Standards\Generic\Sniffs\NamingConventions\UpperCaseConstantNameSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class ConstantNameSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [
T_STRING,
T_CONST,
];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token in the
* stack passed in $tokens.
*
* @return void|int
*/
public function process(File $phpcsFile, $stackPtr)
{
$nameParts = explode('.', basename($phpcsFile->getFilename()));
$fileExtension = end($nameParts);
// Only check in *.module files.
if ($fileExtension !== 'module' && $fileExtension !== 'install') {
return ($phpcsFile->numTokens + 1);
}
$tokens = $phpcsFile->getTokens();
// Only check in the outer scope, not within classes.
if (empty($tokens[$stackPtr]['conditions']) === false) {
return;
}
$moduleName = reset($nameParts);
$expectedStart = strtoupper($moduleName);
if ($tokens[$stackPtr]['code'] === T_CONST) {
// This is a class constant.
$constant = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($constant === false) {
return;
}
$constName = $tokens[$constant]['content'];
if (strpos($constName, $expectedStart) !== 0) {
$warning = 'All constants defined by a module must be prefixed with the module\'s name, expected "%s" but found "%s"';
$data = [
$expectedStart."_$constName",
$constName,
];
$phpcsFile->addWarning($warning, $stackPtr, 'ConstConstantStart', $data);
return;
}//end if
}
// Only interested in define statements now.
if (strtolower($tokens[$stackPtr]['content']) !== 'define') {
return;
}
// Make sure this is not a method call.
$prev = $phpcsFile->findPrevious(T_WHITESPACE, ($stackPtr - 1), null, true);
if ($tokens[$prev]['code'] === T_OBJECT_OPERATOR
|| $tokens[$prev]['code'] === T_DOUBLE_COLON
|| $tokens[$prev]['code'] === T_NULLSAFE_OBJECT_OPERATOR
) {
return;
}
// If the next non-whitespace token after this token
// is not an opening parenthesis then it is not a function call.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($openBracket === false) {
return;
}
// The next non-whitespace token must be the constant name.
$constPtr = $phpcsFile->findNext(T_WHITESPACE, ($openBracket + 1), null, true);
if ($tokens[$constPtr]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
return;
}
// Get the constant name and remove single and double quotes.
$constName = str_replace(["'", '"'], ['', ''], $tokens[$constPtr]['content']);
if (strpos($constName, $expectedStart) !== 0) {
$warning = 'All constants defined by a module must be prefixed with the module\'s name, expected "%s" but found "%s"';
$data = [
$expectedStart."_$constName",
$constName,
];
$phpcsFile->addWarning($warning, $stackPtr, 'ConstantStart', $data);
}//end if
}//end process()
}//end class

View File

@@ -0,0 +1,66 @@
<?php
/**
* \Drupal\Sniffs\Semantics\EmptyInstallSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Throws an error if hook_install() or hook_uninstall() definitions are empty.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class EmptyInstallSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name in the stack.
* name in the stack.
* @param int $functionPtr The position of the function keyword in the stack.
* keyword in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -7));
// Only check in *.install files.
if ($fileExtension !== 'install') {
return;
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -8);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] === ($fileName.'_install')
|| $tokens[$stackPtr]['content'] === ($fileName.'_uninstall')
) {
// Check if there is a function body.
$bodyPtr = $phpcsFile->findNext(
Tokens::$emptyTokens,
($tokens[$functionPtr]['scope_opener'] + 1),
$tokens[$functionPtr]['scope_closer'],
true
);
if ($bodyPtr === false) {
$error = 'Empty installation hooks are not necessary';
$phpcsFile->addError($error, $stackPtr, 'EmptyInstall');
}
}
}//end processFunction()
}//end class

View File

@@ -0,0 +1,211 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionAliasSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that no PHP function name aliases are used.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FunctionAliasSniff extends FunctionCall
{
/**
* Holds all PHP function name aliases (keys) and originals (values). See
* http://php.net/manual/en/aliases.php
*
* @var array<string, string>
*
* cSpell:disable
*/
protected $aliases = [
'_' => 'gettext',
'chop' => 'rtrim',
'close' => 'closedir',
'com_get' => 'com_propget',
'com_propset' => 'com_propput',
'com_set' => 'com_propput',
'die' => 'exit',
'diskfreespace' => 'disk_free_space',
'doubleval' => 'floatval',
'fbsql' => 'fbsql_db_query',
'fputs' => 'fwrite',
'gzputs' => 'gzwrite',
'i18n_convert' => 'mb_convert_encoding',
'i18n_discover_encoding' => 'mb_detect_encoding',
'i18n_http_input' => 'mb_http_input',
'i18n_http_output' => 'mb_http_output',
'i18n_internal_encoding' => 'mb_internal_encoding',
'i18n_ja_jp_hantozen' => 'mb_convert_kana',
'i18n_mime_header_decode' => 'mb_decode_mimeheader',
'i18n_mime_header_encode' => 'mb_encode_mimeheader',
'imap_create' => 'imap_createmailbox',
'imap_fetchtext' => 'imap_body',
'imap_getmailboxes' => 'imap_list_full',
'imap_getsubscribed' => 'imap_lsub_full',
'imap_header' => 'imap_headerinfo',
'imap_listmailbox' => 'imap_list',
'imap_listsubscribed' => 'imap_lsub',
'imap_rename' => 'imap_renamemailbox',
'imap_scan' => 'imap_listscan',
'imap_scanmailbox' => 'imap_listscan',
'ini_alter' => 'ini_set',
'is_double' => 'is_float',
'is_integer' => 'is_int',
'is_long' => 'is_int',
'is_real' => 'is_float',
'is_writeable' => 'is_writable',
'join' => 'implode',
'key_exists' => 'array_key_exists',
'ldap_close' => 'ldap_unbind',
'magic_quotes_runtime' => 'set_magic_quotes_runtime',
'mbstrcut' => 'mb_strcut',
'mbstrlen' => 'mb_strlen',
'mbstrpos' => 'mb_strpos',
'mbstrrpos' => 'mb_strrpos',
'mbsubstr' => 'mb_substr',
'ming_setcubicthreshold' => 'ming_setCubicThreshold',
'ming_setscale' => 'ming_setScale',
'msql' => 'msql_db_query',
'msql_createdb' => 'msql_create_db',
'msql_dbname' => 'msql_result',
'msql_dropdb' => 'msql_drop_db',
'msql_fieldflags' => 'msql_field_flags',
'msql_fieldlen' => 'msql_field_len',
'msql_fieldname' => 'msql_field_name',
'msql_fieldtable' => 'msql_field_table',
'msql_fieldtype' => 'msql_field_type',
'msql_freeresult' => 'msql_free_result',
'msql_listdbs' => 'msql_list_dbs',
'msql_listfields' => 'msql_list_fields',
'msql_listtables' => 'msql_list_tables',
'msql_numfields' => 'msql_num_fields',
'msql_numrows' => 'msql_num_rows',
'msql_regcase' => 'sql_regcase',
'msql_selectdb' => 'msql_select_db',
'msql_tablename' => 'msql_result',
'mssql_affected_rows' => 'sybase_affected_rows',
'mssql_close' => 'sybase_close',
'mssql_connect' => 'sybase_connect',
'mssql_data_seek' => 'sybase_data_seek',
'mssql_fetch_array' => 'sybase_fetch_array',
'mssql_fetch_field' => 'sybase_fetch_field',
'mssql_fetch_object' => 'sybase_fetch_object',
'mssql_fetch_row' => 'sybase_fetch_row',
'mssql_field_seek' => 'sybase_field_seek',
'mssql_free_result' => 'sybase_free_result',
'mssql_get_last_message' => 'sybase_get_last_message',
'mssql_min_client_severity' => 'sybase_min_client_severity',
'mssql_min_error_severity' => 'sybase_min_error_severity',
'mssql_min_message_severity' => 'sybase_min_message_severity',
'mssql_min_server_severity' => 'sybase_min_server_severity',
'mssql_num_fields' => 'sybase_num_fields',
'mssql_num_rows' => 'sybase_num_rows',
'mssql_pconnect' => 'sybase_pconnect',
'mssql_query' => 'sybase_query',
'mssql_result' => 'sybase_result',
'mssql_select_db' => 'sybase_select_db',
'mysql' => 'mysql_db_query',
'mysql_createdb' => 'mysql_create_db',
'mysql_db_name' => 'mysql_result',
'mysql_dbname' => 'mysql_result',
'mysql_dropdb' => 'mysql_drop_db',
'mysql_fieldflags' => 'mysql_field_flags',
'mysql_fieldlen' => 'mysql_field_len',
'mysql_fieldname' => 'mysql_field_name',
'mysql_fieldtable' => 'mysql_field_table',
'mysql_fieldtype' => 'mysql_field_type',
'mysql_freeresult' => 'mysql_free_result',
'mysql_listdbs' => 'mysql_list_dbs',
'mysql_listfields' => 'mysql_list_fields',
'mysql_listtables' => 'mysql_list_tables',
'mysql_numfields' => 'mysql_num_fields',
'mysql_numrows' => 'mysql_num_rows',
'mysql_selectdb' => 'mysql_select_db',
'mysql_tablename' => 'mysql_result',
'oci8append' => 'ocicollappend',
'oci8assign' => 'ocicollassign',
'oci8assignelem' => 'ocicollassignelem',
'oci8close' => 'ocicloselob',
'oci8free' => 'ocifreedesc',
'oci8getelem' => 'ocicollgetelem',
'oci8load' => 'ociloadlob',
'oci8max' => 'ocicollmax',
'oci8ocifreecursor' => 'ocifreestatement',
'oci8save' => 'ocisavelob',
'oci8savefile' => 'ocisavelobfile',
'oci8size' => 'ocicollsize',
'oci8trim' => 'ocicolltrim',
'oci8writetemporary' => 'ociwritetemporarylob',
'oci8writetofile' => 'ociwritelobtofile',
'odbc_do' => 'odbc_exec',
'odbc_field_precision' => 'odbc_field_len',
'pdf_add_outline' => 'pdf_add_bookmark',
'pg_clientencoding' => 'pg_client_encoding',
'pg_setclientencoding' => 'pg_set_client_encoding',
'pos' => 'current',
'recode' => 'recode_string',
'show_source' => 'highlight_file',
'sizeof' => 'count',
'snmpwalkoid' => 'snmprealwalk',
'strchr' => 'strstr',
'xptr_new_context' => 'xpath_new_context',
// cspell:enable
];
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return array_keys($this->aliases);
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
$error = '%s() is a function name alias, use %s() instead';
$name = $tokens[$stackPtr]['content'];
$data = [
$name,
$this->aliases[$name],
];
$phpcsFile->addError($error, $stackPtr, 'FunctionAlias', $data);
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,234 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionCall.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Helper class to sniff for specific function calls.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
abstract class FunctionCall implements Sniff
{
/**
* The currently processed file.
*
* @var \PHP_CodeSniffer\Files\File
*/
protected $phpcsFile;
/**
* The token position of the function call.
*
* @var integer
*/
protected $functionCall;
/**
* The token position of the opening bracket of the function call.
*
* @var integer
*/
protected $openBracket;
/**
* The token position of the closing bracket of the function call.
*
* @var integer
*/
protected $closeBracket;
/**
* Internal cache to save the calculated arguments of the function call.
*
* @var array<int, mixed>
*/
protected $arguments;
/**
* Whether method invocations with the same function name should be processed,
* too.
*
* @var boolean
*/
protected $includeMethodCalls = false;
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_STRING];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
$functionName = $tokens[$stackPtr]['content'];
if (in_array($functionName, $this->registerFunctionNames()) === false) {
// Not interested in this function.
return;
}
if ($this->isFunctionCall($phpcsFile, $stackPtr) === false) {
return;
}
// Find the next non-empty token.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
$this->phpcsFile = $phpcsFile;
$this->functionCall = $stackPtr;
$this->openBracket = $openBracket;
$this->closeBracket = $tokens[$openBracket]['parenthesis_closer'];
$this->arguments = [];
$this->processFunctionCall($phpcsFile, $stackPtr, $openBracket, $this->closeBracket);
}//end process()
/**
* Checks if this is a function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return bool
*/
protected function isFunctionCall(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Find the next non-empty token.
$openBracket = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
if ($tokens[$openBracket]['code'] !== T_OPEN_PARENTHESIS) {
// Not a function call.
return false;
}
if (isset($tokens[$openBracket]['parenthesis_closer']) === false) {
// Not a function call.
return false;
}
// Find the previous non-empty token.
$search = Tokens::$emptyTokens;
$search[] = T_BITWISE_AND;
$previous = $phpcsFile->findPrevious($search, ($stackPtr - 1), null, true);
if ($tokens[$previous]['code'] === T_FUNCTION) {
// It's a function definition, not a function call.
return false;
}
if ($tokens[$previous]['code'] === T_OBJECT_OPERATOR && $this->includeMethodCalls === false) {
// It's a method invocation, not a function call.
return false;
}
if ($tokens[$previous]['code'] === T_DOUBLE_COLON && $this->includeMethodCalls === false) {
// It's a static method invocation, not a function call.
return false;
}
return true;
}//end isFunctionCall()
/**
* Returns start and end token for a given argument number.
*
* @param int $number Indicates which argument should be examined, starting with
* 1 for the first argument.
*
* @return array<string, int>|false
*/
public function getArgument($number)
{
// Check if we already calculated the tokens for this argument.
if (isset($this->arguments[$number]) === true) {
return $this->arguments[$number];
}
$tokens = $this->phpcsFile->getTokens();
// Start token of the first argument.
$start = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($this->openBracket + 1), null, true);
if ($start === $this->closeBracket) {
// Function call has no arguments, so return false.
return false;
}
// End token of the last argument.
$end = $this->phpcsFile->findPrevious(Tokens::$emptyTokens, ($this->closeBracket - 1), null, true);
$lastArgEnd = $end;
$nextSeparator = $this->openBracket;
$counter = 1;
while (($nextSeparator = $this->phpcsFile->findNext(T_COMMA, ($nextSeparator + 1), $this->closeBracket)) !== false) {
// Make sure the comma belongs directly to this function call,
// and is not inside a nested function call or array.
$brackets = $tokens[$nextSeparator]['nested_parenthesis'];
$lastBracket = array_pop($brackets);
if ($lastBracket !== $this->closeBracket) {
continue;
}
// Update the end token of the current argument.
$end = $this->phpcsFile->findPrevious(Tokens::$emptyTokens, ($nextSeparator - 1), null, true);
// Save the calculated findings for the current argument.
$this->arguments[$counter] = [
'start' => $start,
'end' => $end,
];
if ($counter === $number) {
break;
}
$counter++;
$start = $this->phpcsFile->findNext(Tokens::$emptyTokens, ($nextSeparator + 1), null, true);
$end = $lastArgEnd;
}//end while
// If the counter did not reach the passed number something is wrong.
if ($counter !== $number) {
return false;
}
$this->arguments[$counter] = [
'start' => $start,
'end' => $end,
];
return $this->arguments[$counter];
}//end getArgument()
}//end class

View File

@@ -0,0 +1,79 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionDefinition.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Util\Tokens;
/**
* Helper class to sniff for function definitions.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
abstract class FunctionDefinition implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_STRING];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
// Check if this is a function definition.
$functionPtr = $phpcsFile->findPrevious(
Tokens::$emptyTokens,
($stackPtr - 1),
null,
true
);
if ($tokens[$functionPtr]['code'] === T_FUNCTION) {
$this->processFunction($phpcsFile, $stackPtr, $functionPtr);
}
}//end process()
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name in the stack.
* name in the stack.
* @param int $functionPtr The position of the function keyword in the stack.
* keyword in the stack.
*
* @return void
*/
abstract public function processFunction(File $phpcsFile, $stackPtr, $functionPtr);
}//end class

View File

@@ -0,0 +1,181 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionTSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Check the usage of the t() function to not escape translatable strings with back
* slashes. Also checks that the first argument does not use string concatenation.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FunctionTSniff extends FunctionCall
{
/**
* We also want to catch $this->t() calls in Drupal 8.
*
* @var boolean
*/
protected $includeMethodCalls = true;
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return [
't',
'TranslatableMarkup',
'TranslationWrapper',
];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
$argument = $this->getArgument(1);
if ($argument === false) {
$error = 'Empty calls to t() are not allowed';
$phpcsFile->addError($error, $stackPtr, 'EmptyT');
return;
}
if ($tokens[$argument['start']]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
// Not a translatable string literal.
$warning = 'Only string literals should be passed to t() where possible';
$phpcsFile->addWarning($warning, $argument['start'], 'NotLiteralString');
return;
}
$string = $tokens[$argument['start']]['content'];
if ($string === '""' || $string === "''") {
$warning = 'Do not pass empty strings to t()';
$phpcsFile->addWarning($warning, $argument['start'], 'EmptyString');
return;
}
$concatAfter = $phpcsFile->findNext(Tokens::$emptyTokens, ($closeBracket + 1), null, true, null, true);
if ($concatAfter !== false && $tokens[$concatAfter]['code'] === T_STRING_CONCAT) {
$stringAfter = $phpcsFile->findNext(Tokens::$emptyTokens, ($concatAfter + 1), null, true, null, true);
if ($stringAfter !== false
&& $tokens[$stringAfter]['code'] === T_CONSTANT_ENCAPSED_STRING
&& $this->checkConcatString($tokens[$stringAfter]['content']) === false
) {
$warning = 'Do not concatenate strings to translatable strings, they should be part of the t() argument and you should use placeholders';
$phpcsFile->addWarning($warning, $stringAfter, 'ConcatString');
}
}
$lastChar = substr($string, -1);
if ($lastChar === '"' || $lastChar === "'") {
$message = substr($string, 1, -1);
if ($message !== trim($message)) {
$warning = 'Translatable strings must not begin or end with white spaces, use placeholders with t() for variables';
$phpcsFile->addWarning($warning, $argument['start'], 'WhiteSpace');
}
}
$concatFound = $phpcsFile->findNext(T_STRING_CONCAT, $argument['start'], $argument['end']);
if ($concatFound !== false) {
$error = 'Concatenating translatable strings is not allowed, use placeholders instead and only one string literal';
$phpcsFile->addError($error, $concatFound, 'Concat');
}
// Check if there is a backslash escaped single quote in the string and
// if the string makes use of double quotes.
if ($string[0] === "'" && strpos($string, "\'") !== false
&& strpos($string, '"') === false
) {
$warn = 'Avoid backslash escaping in translatable strings when possible, use "" quotes instead';
$phpcsFile->addWarning($warn, $argument['start'], 'BackslashSingleQuote');
return;
}
if ($string[0] === '"' && strpos($string, '\"') !== false
&& strpos($string, "'") === false
) {
$warn = "Avoid backslash escaping in translatable strings when possible, use '' quotes instead";
$phpcsFile->addWarning($warn, $argument['start'], 'BackslashDoubleQuote');
}
}//end processFunctionCall()
/**
* Checks if a string can be concatenated with a translatable string.
*
* @param string $string The string that is concatenated to a t() call.
*
* @return bool
* TRUE if the string is allowed to be concatenated with a translatable
* string, FALSE if not.
*/
protected function checkConcatString($string)
{
// Remove outer quotes, spaces and HTML tags from the original string.
$string = trim($string, '"\'');
$string = trim(strip_tags($string));
if ($string === '') {
return true;
}
$allowedItems = [
'(',
')',
'[',
']',
'-',
'<',
'>',
'«',
'»',
'\n',
];
foreach ($allowedItems as $item) {
if ($item === $string) {
return true;
}
}
return false;
}//end checkConcatString()
}//end class

View File

@@ -0,0 +1,189 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionTriggerErrorSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that the trigger_error deprecation text message adheres to standards.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FunctionTriggerErrorSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['trigger_error'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
file $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
// If no second argument then quit.
if ($this->getArgument(2) === false) {
return;
}
// Only check deprecation messages.
if (strcasecmp($tokens[$this->getArgument(2)['start']]['content'], 'E_USER_DEPRECATED') !== 0) {
return;
}
// Get the first argument passed to trigger_error().
$argument = $this->getArgument(1);
// Skip variable deprecation messages.
if ($tokens[$argument['start']]['code'] === T_VARIABLE) {
return;
}
// Extract the message text to check. If if it formed using sprintf()
// then find the single overall string using ->findNext.
if ($tokens[$argument['start']]['code'] === T_STRING
&& strcasecmp($tokens[$argument['start']]['content'], 'sprintf') === 0
) {
$messagePosition = $phpcsFile->findNext(T_CONSTANT_ENCAPSED_STRING, $argument['start']);
// Remove the quotes using substr, because trim would take multiple
// quotes away and possibly not report a faulty message.
$messageText = substr($tokens[$messagePosition]['content'], 1, ($tokens[$messagePosition]['length'] - 2));
} else {
$messageParts = [];
// If not sprintf() then extract and store all the items except
// whitespace, concatenation operators and comma. This will give all
// real content such as concatenated strings and constants.
for ($i = $argument['start']; $i <= $argument['end']; $i++) {
if (in_array($tokens[$i]['code'], [T_WHITESPACE, T_STRING_CONCAT, T_COMMA]) === false) {
// For strings, remove the quotes using substr not trim.
// Simple strings are T_CONSTANT_ENCAPSED_STRING and strings
// with variable interpolation are T_DOUBLE_QUOTED_STRING.
if ($tokens[$i]['code'] === T_CONSTANT_ENCAPSED_STRING || $tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING) {
$messageParts[] = substr($tokens[$i]['content'], 1, ($tokens[$i]['length'] - 2));
} else {
$messageParts[] = $tokens[$i]['content'];
}
}
}
$messageText = implode(' ', $messageParts);
}//end if
// Check if there is a @deprecated tag in an associated doc comment
// block. If the @trigger_error was level 0 (entire class or file) then
// try to find a doc comment after the trigger_error also at level 0.
// If the @trigger_error was at level > 0 it means it is inside a
// function so search backwards for the function comment block, which
// will be at one level lower.
$strictStandard = false;
$triggerErrorLevel = $tokens[$stackPtr]['level'];
if ($triggerErrorLevel === 0) {
$requiredLevel = 0;
$block = $phpcsFile->findNext(T_DOC_COMMENT_OPEN_TAG, $argument['start']);
} else {
$requiredLevel = ($triggerErrorLevel - 1);
$block = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $argument['start']);
}
if (isset($tokens[$block]['level']) === true
&& $tokens[$block]['level'] === $requiredLevel
&& isset($tokens[$block]['comment_tags']) === true
) {
foreach ($tokens[$block]['comment_tags'] as $tag) {
$strictStandard = $strictStandard || (strtolower($tokens[$tag]['content']) === '@deprecated');
}
}
// The string standard format for @trigger_error() is:
// %thing% is deprecated in %deprecation-version% and is removed in
// %removal-version%. %extra-info%. See %cr-link%
// For the 'relaxed' standard the 'and is removed in' can be replaced
// with any text.
$matches = [];
if ($strictStandard === true) {
// Use (?U) 'ungreedy' before the version so that only the text up
// to the first period followed by a space is matched, as there may
// be more than one sentence in the extra-info part.
preg_match('/(.+) is deprecated in (\S+) (and is removed from) (?U)(.+)\. (.*)\. See (\S+)$/', $messageText, $matches);
$sniff = 'TriggerErrorTextLayoutStrict';
$error = "The trigger_error message '%s' does not match the strict standard format: %%thing%% is deprecated in %%deprecation-version%% and is removed from %%removal-version%%. %%extra-info%%. See %%cr-link%%";
} else {
// Allow %extra-info% to be empty as this is optional in the relaxed
// version.
preg_match('/(.+) is deprecated in (\S+) (?U)(.+) (\S+)\. (.*)See (\S+)$/', $messageText, $matches);
$sniff = 'TriggerErrorTextLayoutRelaxed';
$error = "The trigger_error message '%s' does not match the relaxed standard format: %%thing%% is deprecated in %%deprecation-version%% any free text %%removal-version%%. %%extra-info%%. See %%cr-link%%";
}
// There should be 7 items in $matches: 0 is full text, 1 = thing,
// 2 = deprecation-version, 3 = middle text, 4 = removal-version,
// 5 = extra-info, 6 = cr-link.
if (count($matches) !== 7) {
$phpcsFile->addError($error, $argument['start'], $sniff, [$messageText]);
} else {
// The text follows the basic layout. Now check that the version
// matches drupal:n.n.n or project:n.x-n.n or project:n.x-n.n-label[n]
// or project:n.n.n or project:n.n.n-label[n]. The text must be all
// lower case and numbers can be one or two digits.
foreach (['deprecation-version' => $matches[2], 'removal-version' => $matches[4]] as $name => $version) {
if (preg_match('/^[a-z\d_]+:(\d{1,2}\.\d{1,2}\.\d{1,2}|\d{1,2}\.x\-\d{1,2}\.\d{1,2})(-[a-z]{1,5}\d{1,2})?$/', $version) === 0) {
$error = "The %s '%s' does not match the lower-case machine-name standard: drupal:n.n.n or project:n.x-n.n or project:n.x-n.n-label[n] or project:n.n.n or project:n.n.n-label[n]";
$phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorVersion', [$name, $version]);
}
}
// Check the 'See' link.
$crLink = $matches[6];
// Allow for the alternative 'node' or 'project/aaa/issues' format.
preg_match('[^http(s*)://www.drupal.org/(node|project/\w+/issues)/(\d+)(\.*)$]', $crLink, $crMatches);
// If cr_matches[4] is not blank it means that the url is correct
// but it ends with a period. As this can be a common mistake give a
// specific message to assist in fixing.
if (isset($crMatches[4]) === true && empty($crMatches[4]) === false) {
$error = "The url '%s' should not end with a period.";
$phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorPeriodAfterSeeUrl', [$crLink]);
} else if (empty($crMatches) === true) {
$error = "The url '%s' does not match the standard: http(s)://www.drupal.org/node/n or http(s)://www.drupal.org/project/aaa/issues/n";
$phpcsFile->addWarning($error, $argument['start'], 'TriggerErrorSeeUrlFormat', [$crLink]);
}
}//end if
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,81 @@
<?php
/**
* \Drupal\Sniffs\Semantics\FunctionWatchdogSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that the second argument to watchdog() is not enclosed with t().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class FunctionWatchdogSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['watchdog'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
// Get the second argument passed to watchdog().
$argument = $this->getArgument(2);
if ($argument === false) {
$error = 'The second argument to watchdog() is missing';
$phpcsFile->addError($error, $stackPtr, 'WatchdogArgument');
return;
}
if ($tokens[$argument['start']]['code'] === T_STRING
&& $tokens[$argument['start']]['content'] === 't'
) {
$error = 'The second argument to watchdog() should not be enclosed with t()';
$phpcsFile->addError($error, $argument['start'], 'WatchdogT');
}
$concatFound = $phpcsFile->findNext(T_STRING_CONCAT, $argument['start'], $argument['end']);
if ($concatFound !== false) {
$error = 'Concatenating translatable strings is not allowed, use placeholders instead and only one string literal';
$phpcsFile->addError($error, $concatFound, 'Concat');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,63 @@
<?php
/**
* \Drupal\Sniffs\Semantics\InstallHooksSniff
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that hook_disable(), hook_enable(), hook_install(), hook_uninstall(),
* hook_requirements() and hook_schema() are not defined in the module file.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class InstallHooksSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name in the stack.
* name in the stack.
* @param int $functionPtr The position of the function keyword in the stack.
* keyword in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -6));
// Only check in *.module files.
if ($fileExtension !== 'module') {
return;
}
$tokens = $phpcsFile->getTokens();
$fileName = substr(basename($phpcsFile->getFilename()), 0, -7);
if ($tokens[$stackPtr]['content'] === ($fileName.'_install')
|| $tokens[$stackPtr]['content'] === ($fileName.'_uninstall')
|| $tokens[$stackPtr]['content'] === ($fileName.'_requirements')
|| $tokens[$stackPtr]['content'] === ($fileName.'_schema')
|| $tokens[$stackPtr]['content'] === ($fileName.'_enable')
|| $tokens[$stackPtr]['content'] === ($fileName.'_disable')
) {
$error = '%s() is an installation hook and must be declared in an install file';
$data = [$tokens[$stackPtr]['content']];
$phpcsFile->addError($error, $stackPtr, 'InstallHook', $data);
}
}//end processFunction()
}//end class

View File

@@ -0,0 +1,70 @@
<?php
/**
* \Drupal\Sniffs\Semantics\LStringTranslatableSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that string literals passed to l() are translatable.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class LStringTranslatableSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['l'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
// Get the first argument passed to l().
$argument = $this->getArgument(1);
if ($tokens[$argument['start']]['code'] === T_CONSTANT_ENCAPSED_STRING
// If the string starts with a HTML tag we don't complain.
&& $tokens[$argument['start']]['content'][1] !== '<'
) {
$error = 'The $text argument to l() should be enclosed within t() so that it is translatable';
$phpcsFile->addError($error, $stackPtr, 'LArg');
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,100 @@
<?php
/**
* \Drupal\Sniffs\Semantics\PregSecuritySniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Check the usage of the preg functions to ensure the insecure /e flag isn't
* used: https://www.drupal.org/node/750148
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class PregSecuritySniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return [
'preg_filter',
'preg_grep',
'preg_match',
'preg_match_all',
'preg_replace',
'preg_replace_callback',
'preg_split',
];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
File $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
$argument = $this->getArgument(1);
if ($argument === false) {
return;
}
if ($tokens[$argument['start']]['code'] !== T_CONSTANT_ENCAPSED_STRING) {
// Not a string literal.
// @TODO: Extend code to recognize patterns in variables.
return;
}
$pattern = $tokens[$argument['start']]['content'];
$quote = substr($pattern, 0, 1);
// Check that the pattern is a string.
if ($quote === '"' || $quote === "'") {
// Get the delimiter - first char after the enclosing quotes.
$delimiter = preg_quote(substr($pattern, 1, 1), '/');
// Check if there is the evil e flag.
if (preg_match('/'.$delimiter.'[\w]{0,}e[\w]{0,}$/', substr($pattern, 0, -1)) === 1) {
$warn = 'Using the e flag in %s is a possible security risk. For details see https://www.drupal.org/node/750148';
$phpcsFile->addError(
$warn,
$argument['start'],
'PregEFlag',
[$tokens[$stackPtr]['content']]
);
return;
}
}
}//end processFunctionCall()
}//end class

View File

@@ -0,0 +1,60 @@
<?php
/**
* \Drupal\Sniffs\Semantics\RemoteAddressSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Make sure that ip_address() or Drupal::request()->getClientIp() is used instead of
* $_SERVER['REMOTE_ADDR'].
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class RemoteAddressSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array<int|string>
*/
public function register()
{
return [T_VARIABLE];
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The current file being processed.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr)
{
$string = $phpcsFile->getTokensAsString($stackPtr, 4);
$startOfStatement = $phpcsFile->findStartOfStatement($stackPtr);
if (($string === '$_SERVER["REMOTE_ADDR"]' || $string === '$_SERVER[\'REMOTE_ADDR\']') && $stackPtr !== $startOfStatement) {
$error = 'Use ip_address() or Drupal::request()->getClientIp() instead of $_SERVER[\'REMOTE_ADDR\']';
$phpcsFile->addError($error, $stackPtr, 'RemoteAddress');
}
}//end process()
}//end class

View File

@@ -0,0 +1,83 @@
<?php
/**
* \Drupal\Sniffs\Semantics\TInHookMenuSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that t() is not used in hook_menu().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class TInHookMenuSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name in the stack.
* name in the stack.
* @param int $functionPtr The position of the function keyword in the stack.
* keyword in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -6));
// Only check in *.module files.
if ($fileExtension !== 'module') {
return;
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -7);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== ($fileName.'_menu')) {
return;
}
// Search in the function body for t() calls.
$string = $phpcsFile->findNext(
T_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if ($tokens[$string]['content'] === 't') {
$opener = $phpcsFile->findNext(
Tokens::$emptyTokens,
($string + 1),
null,
true
);
if ($opener !== false
&& $tokens[$opener]['code'] === T_OPEN_PARENTHESIS
) {
$error = 'Do not use t() in hook_menu()';
$phpcsFile->addError($error, $string, 'TFound');
}
}
$string = $phpcsFile->findNext(
T_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,83 @@
<?php
/**
* \Drupal\Sniffs\Semantics\TInHookSchemaSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Util\Tokens;
/**
* Checks that t() is not used in hook_schema().
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class TInHookSchemaSniff extends FunctionDefinition
{
/**
* Process this function definition.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function name in the stack.
* name in the stack.
* @param int $functionPtr The position of the function keyword in the stack.
* keyword in the stack.
*
* @return void
*/
public function processFunction(File $phpcsFile, $stackPtr, $functionPtr)
{
$fileExtension = strtolower(substr($phpcsFile->getFilename(), -7));
// Only check in *.install files.
if ($fileExtension !== 'install') {
return;
}
$fileName = substr(basename($phpcsFile->getFilename()), 0, -8);
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] !== ($fileName.'_schema')) {
return;
}
// Search in the function body for t() calls.
$string = $phpcsFile->findNext(
T_STRING,
$tokens[$functionPtr]['scope_opener'],
$tokens[$functionPtr]['scope_closer']
);
while ($string !== false) {
if ($tokens[$string]['content'] === 't') {
$opener = $phpcsFile->findNext(
Tokens::$emptyTokens,
($string + 1),
null,
true
);
if ($opener !== false
&& $tokens[$opener]['code'] === T_OPEN_PARENTHESIS
) {
$error = 'Do not use t() in hook_schema(), this will only generate overhead for translators';
$phpcsFile->addError($error, $string, 'TFound');
}
}
$string = $phpcsFile->findNext(
T_STRING,
($string + 1),
$tokens[$functionPtr]['scope_closer']
);
}//end while
}//end processFunction()
}//end class

View File

@@ -0,0 +1,81 @@
<?php
/**
* \Drupal\Sniffs\Semantics\UnsilencedDeprecationSniff.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace Drupal\Sniffs\Semantics;
use PHP_CodeSniffer\Files\File;
/**
* Checks that the trigger_error deprecation is silenced by a preceding '@'.
*
* @category PHP
* @package PHP_CodeSniffer
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class UnsilencedDeprecationSniff extends FunctionCall
{
/**
* Returns an array of function names this test wants to listen for.
*
* @return array<string>
*/
public function registerFunctionNames()
{
return ['trigger_error'];
}//end registerFunctionNames()
/**
* Processes this function call.
*
* @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the function call in
* the stack.
* @param int $openBracket The position of the opening
* parenthesis in the stack.
* @param int $closeBracket The position of the closing
* parenthesis in the stack.
*
* @return void
*/
public function processFunctionCall(
file $phpcsFile,
$stackPtr,
$openBracket,
$closeBracket
) {
$tokens = $phpcsFile->getTokens();
$argument = $this->getArgument(2);
// If no second argument then quit.
if ($argument === false) {
return;
}
// Only check deprecation messages.
if (strcasecmp($tokens[$argument['start']]['content'], 'E_USER_DEPRECATED') !== 0) {
return;
}
if ($tokens[($stackPtr - 1)]['type'] !== 'T_ASPERAND') {
$error = 'All trigger_error calls used for deprecation must be prefixed by an "@"';
$fix = $phpcsFile->addFixableError($error, $stackPtr, 'UnsilencedDeprecation');
if ($fix === true) {
$phpcsFile->fixer->addContentBefore($stackPtr, '@');
}
}
}//end processFunctionCall()
}//end class