1<?php 2/** 3 * Squiz_Sniffs_CSS_ShorthandSizeSniff. 4 * 5 * PHP version 5 6 * 7 * @category PHP 8 * @package PHP_CodeSniffer 9 * @author Greg Sherwood <gsherwood@squiz.net> 10 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 11 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 12 * @link http://pear.php.net/package/PHP_CodeSniffer 13 */ 14 15/** 16 * Squiz_Sniffs_CSS_ShorthandSizeSniff. 17 * 18 * Ensure sizes are defined using shorthand notation where possible, except in the 19 * case where shorthand becomes 3 values. 20 * 21 * @category PHP 22 * @package PHP_CodeSniffer 23 * @author Greg Sherwood <gsherwood@squiz.net> 24 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 25 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 26 * @version Release: @package_version@ 27 * @link http://pear.php.net/package/PHP_CodeSniffer 28 */ 29class Squiz_Sniffs_CSS_ShorthandSizeSniff implements PHP_CodeSniffer_Sniff 30{ 31 32 /** 33 * A list of tokenizers this sniff supports. 34 * 35 * @var array 36 */ 37 public $supportedTokenizers = array('CSS'); 38 39 /** 40 * A list of styles that we shouldn't check. 41 * 42 * These have values that looks like sizes, but are not. 43 * 44 * @var array 45 */ 46 protected $excludeStyles = array( 47 'background-position' => 'background-position', 48 'box-shadow' => 'box-shadow', 49 'transform-origin' => 'transform-origin', 50 '-webkit-transform-origin' => '-webkit-transform-origin', 51 '-ms-transform-origin' => '-ms-transform-origin', 52 ); 53 54 55 /** 56 * Returns the token types that this sniff is interested in. 57 * 58 * @return int[] 59 */ 60 public function register() 61 { 62 return array(T_STYLE); 63 64 }//end register() 65 66 67 /** 68 * Processes the tokens that this sniff is interested in. 69 * 70 * @param PHP_CodeSniffer_File $phpcsFile The file where the token was found. 71 * @param int $stackPtr The position in the stack where 72 * the token was found. 73 * 74 * @return void 75 */ 76 public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) 77 { 78 $tokens = $phpcsFile->getTokens(); 79 80 // Some styles look like shorthand but are not actually a set of 4 sizes. 81 $style = strtolower($tokens[$stackPtr]['content']); 82 if (isset($this->excludeStyles[$style]) === true) { 83 return; 84 } 85 86 // Get the whole style content. 87 $end = $phpcsFile->findNext(T_SEMICOLON, ($stackPtr + 1)); 88 $origContent = $phpcsFile->getTokensAsString(($stackPtr + 1), ($end - $stackPtr - 1)); 89 $origContent = trim($origContent, ': '); 90 91 // Account for a !important annotation. 92 $content = $origContent; 93 if (substr($content, -10) === '!important') { 94 $content = substr($content, 0, -10); 95 $content = trim($content); 96 } 97 98 // Check if this style value is a set of numbers with optional prefixes. 99 $content = preg_replace('/\s+/', ' ', $content); 100 $values = array(); 101 $num = preg_match_all( 102 '/([0-9]+)([a-zA-Z]{2}\s+|%\s+|\s+)/', 103 $content.' ', 104 $values, 105 PREG_SET_ORDER 106 ); 107 108 // Only interested in styles that have multiple sizes defined. 109 if ($num < 2) { 110 return; 111 } 112 113 // Rebuild the content we matched to ensure we got everything. 114 $matched = ''; 115 foreach ($values as $value) { 116 $matched .= $value[0]; 117 } 118 119 if ($content !== trim($matched)) { 120 return; 121 } 122 123 if ($num === 3) { 124 $expected = trim($content.' '.$values[1][1].$values[1][2]); 125 $error = 'Shorthand syntax not allowed here; use %s instead'; 126 $data = array($expected); 127 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotAllowed', $data); 128 129 if ($fix === true) { 130 $phpcsFile->fixer->beginChangeset(); 131 if (substr($origContent, -10) === '!important') { 132 $expected .= ' !important'; 133 } 134 135 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 2), null, true); 136 $phpcsFile->fixer->replaceToken($next, $expected); 137 for ($next++; $next < $end; $next++) { 138 $phpcsFile->fixer->replaceToken($next, ''); 139 } 140 141 $phpcsFile->fixer->endChangeset(); 142 } 143 144 return; 145 }//end if 146 147 if ($num === 2) { 148 if ($values[0][0] !== $values[1][0]) { 149 // Both values are different, so it is already shorthand. 150 return; 151 } 152 } else if ($values[0][0] !== $values[2][0] || $values[1][0] !== $values[3][0]) { 153 // Can't shorthand this. 154 return; 155 } 156 157 if ($values[0][0] === $values[1][0]) { 158 // All values are the same. 159 $expected = $values[0][0]; 160 } else { 161 $expected = $values[0][0].' '.$values[1][0]; 162 } 163 164 $expected = preg_replace('/\s+/', ' ', trim($expected)); 165 166 $error = 'Size definitions must use shorthand if available; expected "%s" but found "%s"'; 167 $data = array( 168 $expected, 169 $content, 170 ); 171 172 $fix = $phpcsFile->addFixableError($error, $stackPtr, 'NotUsed', $data); 173 if ($fix === true) { 174 $phpcsFile->fixer->beginChangeset(); 175 if (substr($origContent, -10) === '!important') { 176 $expected .= ' !important'; 177 } 178 179 $next = $phpcsFile->findNext(T_WHITESPACE, ($stackPtr + 2), null, true); 180 $phpcsFile->fixer->replaceToken($next, $expected); 181 for ($next++; $next < $end; $next++) { 182 $phpcsFile->fixer->replaceToken($next, ''); 183 } 184 185 $phpcsFile->fixer->endChangeset(); 186 } 187 188 }//end process() 189 190 191}//end class 192