ion/build/CodeIgniter/Sniffs/Strings/DoubleQuoteUsageSniff.php

465 lines
16 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
*
* PHP version 5
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
namespace CodeIgniter\Sniffs\Strings;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
2018-01-17 15:32:41 -05:00
use Exception;
/**
* CodeIgniter_Sniffs_Strings_DoubleQuoteUsageSniff.
*
* Ensures that double-quoted strings are used only to parse variables,
* to avoid escape characters before single quotes or for chars that need
* to be interpreted like \r, \n or \t.
* If a double-quoted string contain both single and double quotes
* but no variable, then a warning is raised to encourage the use of
* single-quoted strings.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class VariableUsageSniff implements Sniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
/*
return array(
T_DOUBLE_QUOTED_STRING,
T_CONSTANT_ENCAPSED_STRING,
);
*/
return array();
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current 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();
$string = $tokens[$stackPtr]['content'];
// makes sure that it is about a double quote string,
// since variables are not parsed out of double quoted string
$openDblQtStr = substr($string, 0, 1);
if (0 === strcmp($openDblQtStr, '"')) {
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $string);
} else if (0 === strcmp($openDblQtStr, "'")) {
$this->processSingleQuotedString($phpcsFile, $stackPtr, $string);
}
}//end process()
/**
* Processes this test, when the token encountered is a double-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $dblQtString The double-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $dblQtString)
{
$variableFound = FALSE;
$strTokens = token_get_all('<?php '.$dblQtString);
$strPtr = 1; // skip php opening tag added by ourselves
$requireDblQuotes = FALSE;
while ($strPtr < count($strTokens)) {
$strToken = $strTokens[$strPtr];
if (is_array($strToken)) {
if (in_array($strToken[0], array(T_DOLLAR_OPEN_CURLY_BRACES, T_CURLY_OPEN))) {
$strPtr++;
try {
$this->_parseVariable($strTokens, $strPtr);
} catch (Exception $err) {
$error = 'There is no variable, object nor array between curly braces. Please use the escape char for $ or {.';
2018-01-17 15:32:41 -05:00
$phpcsFile->addError($error, $stackPtr, 1987234);
}
$variableFound = TRUE;
if ('}' !== $strTokens[$strPtr]) {
$error = 'There is no matching closing curly brace.';
2018-01-17 15:32:41 -05:00
$phpcsFile->addError($error, $stackPtr, 987234);
}
// don't move forward, since it will be done in the main loop
// $strPtr++;
} else if (T_VARIABLE === $strToken[0]) {
$variableFound = TRUE;
$error = "Variable {$strToken[1]} in double-quoted strings should be enclosed with curly braces. Please consider {{$strToken[1]}}";
2018-01-17 15:32:41 -05:00
$phpcsFile->addError($error, $stackPtr, 29087234);
}
}
$strPtr++;
}
return $variableFound;
}//end processDoubleQuotedString()
/**
* Processes this test, when the token encountered is a single-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $sglQtString The single-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $sglQtString)
{
$variableFound = FALSE;
$strTokens = token_get_all('<?php '.$sglQtString);
$strPtr = 1; // skip php opening tag added by ourselves
while ($strPtr < count($strTokens)) {
$strToken = $strTokens[$strPtr];
if (is_array($strToken)) {
if (T_VARIABLE === $strToken[0]) {
$error = "Variables like {$strToken[1]} should be in double-quoted strings only.";
2018-01-17 15:32:41 -05:00
$phpcsFile->addError($error, $stackPtr, 12343);
}
}
$strPtr++;
}
return $variableFound;
}//end processSingleQuotedString()
/**
* Grammar rule to parse the use of a variable. Please notice that it
* doesn't manage the leading $.
*
* _parseVariable ::= <variable>
* | <variable>_parseObjectAttribute()
* | <variable>_parseArrayIndexes()
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return array The attribute name associated to index 'var', an array with
* indexes 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
*/
private function _parseVariable ($strTokens, &$strPtr)
{
if ( ! in_array($strTokens[$strPtr][0], array(T_VARIABLE, T_STRING_VARNAME))) {
throw new Exception ('Expected variable name.');
}
$var = $strTokens[$strPtr][1];
$strPtr++;
$startStrPtr = $strPtr;
try {
$attr = $this->_parseObjectAttribute($strTokens, $strPtr);
return array ('obj' => $var, 'attr' => $attr);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
try {
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
return array ('arr' => $var, 'idx' => $idx);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
return array ('var' => $var);
}//end _parseVariable()
/**
* Grammar rule to parse the use of an object attribute.
*
* _parseObjectAttribute ::= -><attribute>
* | -><attribute>_parseObjectAttribute()
* | -><attribute>_parseArrayIndexes()
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return mixed The attribute name as a string, an array with indexes
* 'obj' and 'attr' or an array with indexes 'arr' and 'idx'.
*/
private function _parseObjectAttribute ($strTokens, &$strPtr)
{
if (T_OBJECT_OPERATOR !== $strTokens[$strPtr][0]) {
throw new Exception ('Expected ->.');
}
$strPtr++;
if (T_STRING !== $strTokens[$strPtr][0]) {
throw new Exception ('Expected an object attribute.');
}
$attr = $strTokens[$strPtr][1];
$strPtr++;
$startStrPtr = $strPtr;
try {
$sub_attr = $this->_parseObjectAttribute($strTokens, $strPtr);
return array ('obj' => $attr, 'attr' => $sub_attr);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
try {
$idx = $this->_parseArrayIndexes($strTokens, $strPtr);
return array ('arr' => $attr, 'idx' => $idx);
} catch (Exception $err) {
if ($strPtr !== $startStrPtr) {
throw $err;
}
}
return $attr;
}//end _parseObjectAttribute()
/**
* Grammar rule to parse the use of one or more array indexes.
*
* _parseArrayIndexes ::= _parseArrayIndex()+
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return array Indexes in the same order as in the string.
*/
private function _parseArrayIndexes ($strTokens, &$strPtr)
{
$indexes = array($this->_parseArrayIndex($strTokens, $strPtr));
try {
while (1) {
$startStrPtr = $strPtr;
$indexes [] = $this->_parseArrayIndex($strTokens, $strPtr);
}
} catch (Exception $err) {
if (0 !== ($strPtr - $startStrPtr)) {
throw $err;
}
return $indexes;
}
}//end _parseArrayIndexes()
/**
* Grammar rule to parse the use of array index.
*
* _parseArrayIndex ::= [<index>]
*
* @exception Exception raised if $strTokens starting from $strPtr
* doesn't matched the rule.
*
* @param array $strTokens Tokens to parse.
* @param int $strPtr Pointer to the token where parsing starts.
*
* @return string Index between the 2 square brackets
*/
private function _parseArrayIndex ($strTokens, &$strPtr)
{
if ('[' !== $strTokens[$strPtr]) {
throw new Exception ('Expected [.');
}
$strPtr++;
if (! in_array($strTokens[$strPtr][0], array(T_CONSTANT_ENCAPSED_STRING, T_LNUMBER))) {
throw new Exception ('Expected an array index.');
}
$index = $strTokens[$strPtr][1];
$strPtr++;
if (']' !== $strTokens[$strPtr]) {
throw new Exception ('Expected ].');
}
$strPtr++;
return $index;
}//end _parseArrayIndex()
}//end class
/**
* CodeIgniter_Sniffs_Strings_VariableUsageSniff.
*
* Ensures that variables parsed in double-quoted strings are enclosed with
* braces to prevent greedy token parsing.
* Single-quoted strings don't parse variables, so there is no risk of greedy
* token parsing.
*
* @category PHP
* @package PHP_CodeSniffer
* @author Thomas Ernest <thomas.ernest@baobaz.com>
* @copyright 2011 Thomas Ernest
* @license http://thomas.ernest.fr/developement/php_cs/licence GNU General Public License
* @link http://pear.php.net/package/PHP_CodeSniffer
*/
class DoubleQuoteUsageSniff extends VariableUsageSniff
{
/**
* Returns an array of tokens this test wants to listen for.
*
* @return array
*/
public function register()
{
return array(
T_DOUBLE_QUOTED_STRING,
T_CONSTANT_ENCAPSED_STRING,
);
}//end register()
/**
* Processes this test, when one of its tokens is encountered.
*
* @param File $phpcsFile The current 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)
{
// no variable are in the string from here
$tokens = $phpcsFile->getTokens();
$qtString = $tokens[$stackPtr]['content'];
// makes sure that it is about a double quote string,
// since variables are not parsed out of double quoted string
$open_qt_str = substr($qtString, 0, 1);
// clean the enclosing quotes
$qtString = substr($qtString, 1, strlen($qtString) - 1 - 1);
if (0 === strcmp($open_qt_str, '"')) {
$this->processDoubleQuotedString($phpcsFile, $stackPtr, $qtString);
} else if (0 === strcmp($open_qt_str, "'")) {
$this->processSingleQuotedString($phpcsFile, $stackPtr, $qtString);
}
}//end process()
/**
* Processes this test, when the token encountered is a double-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $qtString The double-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processDoubleQuotedString (File $phpcsFile, $stackPtr, $qtString)
{
// so there should be at least a single quote or a special char
// if there are the 2 kinds of quote and no special char, then add a warning
$has_variable = parent::processDoubleQuotedString($phpcsFile, $stackPtr, '"'.$qtString.'"');
$has_specific_sequence = $this->_hasSpecificSequence($qtString);
$dbl_qt_at = strpos($qtString, '"');
$smpl_qt_at = strpos($qtString, "'");
if (false === $has_variable && false === $has_specific_sequence
&& false === $smpl_qt_at
) {
$error = 'Single-quoted strings should be used unless it contains variables, special chars like \n or single quotes.';
$phpcsFile->addError($error, $stackPtr, 1982);
} else if (false !== $smpl_qt_at && false !== $dbl_qt_at
&& false === $has_variable && false === $has_specific_sequence
) {
$warning = 'It is encouraged to use a single-quoted string, since it doesn\'t contain any variable nor special char though it mixes single and double quotes.';
$phpcsFile->addWarning($warning, $stackPtr, 1982734);
}
}//end processDoubleQuotedString()
/**
* Processes this test, when the token encountered is a single-quoted string.
*
* @param File $phpcsFile The current file being scanned.
* @param int $stackPtr The position of the current token
* in the stack passed in $tokens.
* @param string $qtString The single-quoted string content,
* i.e. without quotes.
*
* @return void
*/
protected function processSingleQuotedString (File $phpcsFile, $stackPtr, $qtString)
{
// if there is single quotes without additional double quotes,
// then user is allowed to use double quote to avoid having to
// escape single quotes. Don't add the warning, if an error was
// already added, because a variable was found in a single-quoted
// string.
$has_variable = parent::processSingleQuotedString($phpcsFile, $stackPtr, "'".$qtString."'");
$dbl_qt_at = strpos($qtString, '"');
$smpl_qt_at = strpos($qtString, "'");
if (false === $has_variable && false !== $smpl_qt_at && false === $dbl_qt_at) {
$warning = 'You may also use double-quoted strings if the string contains single quotes, so you do not have to use escape characters.';
$phpcsFile->addWarning($warning, $stackPtr, 98723);
}
}//end processSingleQuotedString()
/**
* Return TRUE, if a sequence of chars that is parsed in a specific way
* in double-quoted strings is found, FALSE otherwise.
*
* @param string $string String in which sequence of special chars will
* be researched.
*
* @return TRUE, if a sequence of chars that is parsed in a specific way
* in double-quoted strings is found, FALSE otherwise.
*
* @link http://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.double
*/
private function _hasSpecificSequence($string)
{
$hasSpecificSequence = FALSE;
$specialMeaningStrs = array('\n', '\r', '\t', '\v', '\f');
foreach ($specialMeaningStrs as $splStr) {
if (FALSE !== strpos($string, $splStr)) {
$hasSpecificSequence = TRUE;
}
}
$specialMeaningPtrns = array('\[0-7]{1,3}', '\x[0-9A-Fa-f]{1,2}');
foreach ($specialMeaningPtrns as $splPtrn) {
if (1 === preg_match("/{$splPtrn}/", $string)) {
$hasSpecificSequence = TRUE;
}
}
return $hasSpecificSequence;
}//end _hasSpecificSequence()
}//end class