* @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; 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 * @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('_parseVariable($strTokens, $strPtr); } catch (Exception $err) { $error = 'There is no variable, object nor array between curly braces. Please use the escape char for $ or {.'; $phpcsFile->addError($error, $stackPtr, 234); } $variableFound = TRUE; if ('}' !== $strTokens[$strPtr]) { $error = 'There is no matching closing curly brace.'; $phpcsFile->addError($error, $stackPtr, 345); } // 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]}}"; $phpcsFile->addError($error, $stackPtr, 456); } } $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('addError($error, $stackPtr); } } $strPtr++; } return $variableFound; }//end processSingleQuotedString() /** * Grammar rule to parse the use of a variable. Please notice that it * doesn't manage the leading $. * * _parseVariable ::= * | _parseObjectAttribute() * | _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 ::= -> * | ->_parseObjectAttribute() * | ->_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 ::= [] * * @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 * @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); } 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); } }//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); } }//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 ?>