Util.php (10187B)
1 <?php 2 /** 3 * PHP_CodeCoverage 4 * 5 * Copyright (c) 2009-2014, Sebastian Bergmann <sebastian@phpunit.de>. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * * Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * * Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * * Neither the name of Sebastian Bergmann nor the names of his 21 * contributors may be used to endorse or promote products derived 22 * from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 35 * POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @category PHP 38 * @package CodeCoverage 39 * @author Sebastian Bergmann <sebastian@phpunit.de> 40 * @copyright 2009-2014 Sebastian Bergmann <sebastian@phpunit.de> 41 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 42 * @link http://github.com/sebastianbergmann/php-code-coverage 43 * @since File available since Release 1.0.0 44 */ 45 46 /** 47 * Utility methods. 48 * 49 * @category PHP 50 * @package CodeCoverage 51 * @author Sebastian Bergmann <sebastian@phpunit.de> 52 * @copyright 2009-2014 Sebastian Bergmann <sebastian@phpunit.de> 53 * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License 54 * @link http://github.com/sebastianbergmann/php-code-coverage 55 * @since Class available since Release 1.0.0 56 */ 57 class PHP_CodeCoverage_Util 58 { 59 /** 60 * @var array 61 */ 62 protected static $ignoredLines = array(); 63 64 /** 65 * @var array 66 */ 67 protected static $ids = array(); 68 69 70 /** 71 * Returns the lines of a source file that should be ignored. 72 * 73 * @param string $filename 74 * @param boolean $cacheTokens 75 * @return array 76 * @throws PHP_CodeCoverage_Exception 77 */ 78 public static function getLinesToBeIgnored($filename, $cacheTokens = TRUE) 79 { 80 if (!is_string($filename)) { 81 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 82 1, 'string' 83 ); 84 } 85 86 if (!is_bool($cacheTokens)) { 87 throw PHP_CodeCoverage_Util_InvalidArgumentHelper::factory( 88 2, 'boolean' 89 ); 90 } 91 92 if (!isset(self::$ignoredLines[$filename])) { 93 self::$ignoredLines[$filename] = array(); 94 $ignore = FALSE; 95 $stop = FALSE; 96 $lines = file($filename); 97 98 foreach ($lines as $index => $line) { 99 if (!trim($line)) { 100 self::$ignoredLines[$filename][$index+1] = TRUE; 101 } 102 } 103 104 if ($cacheTokens) { 105 $tokens = PHP_Token_Stream_CachingFactory::get($filename); 106 } else { 107 $tokens = new PHP_Token_Stream($filename); 108 } 109 110 $classes = array_merge($tokens->getClasses(), $tokens->getTraits()); 111 $tokens = $tokens->tokens(); 112 113 foreach ($tokens as $token) { 114 switch (get_class($token)) { 115 case 'PHP_Token_COMMENT': 116 case 'PHP_Token_DOC_COMMENT': { 117 118 $_token = trim($token); 119 $_line = trim($lines[$token->getLine() - 1]); 120 121 if ($_token == '// @codeCoverageIgnore' || 122 $_token == '//@codeCoverageIgnore') { 123 $ignore = TRUE; 124 $stop = TRUE; 125 } 126 127 else if ($_token == '// @codeCoverageIgnoreStart' || 128 $_token == '//@codeCoverageIgnoreStart') { 129 $ignore = TRUE; 130 } 131 132 else if ($_token == '// @codeCoverageIgnoreEnd' || 133 $_token == '//@codeCoverageIgnoreEnd') { 134 $stop = TRUE; 135 } 136 137 // be sure the comment doesn't have some token BEFORE it on the same line... 138 // it would not be safe to ignore the whole line in those cases. 139 if (0 === strpos($_token, $_line)) { 140 $count = substr_count($token, "\n"); 141 $line = $token->getLine(); 142 143 for ($i = $line; $i < $line + $count; $i++) { 144 self::$ignoredLines[$filename][$i] = TRUE; 145 } 146 147 if ($token instanceof PHP_Token_DOC_COMMENT) { 148 // Workaround for the fact the DOC_COMMENT token 149 // does not include the final \n character in its 150 // text. 151 if (substr(trim($lines[$i-1]), -2) == '*/') { 152 self::$ignoredLines[$filename][$i] = TRUE; 153 } 154 } 155 } 156 } 157 break; 158 159 case 'PHP_Token_INTERFACE': 160 case 'PHP_Token_TRAIT': 161 case 'PHP_Token_CLASS': 162 case 'PHP_Token_FUNCTION': { 163 $docblock = $token->getDocblock(); 164 165 if (strpos($docblock, '@codeCoverageIgnore')) { 166 $endLine = $token->getEndLine(); 167 168 for ($i = $token->getLine(); $i <= $endLine; $i++) { 169 self::$ignoredLines[$filename][$i] = TRUE; 170 } 171 } 172 173 else if ($token instanceof PHP_Token_INTERFACE || 174 $token instanceof PHP_Token_TRAIT || 175 $token instanceof PHP_Token_CLASS) { 176 if (empty($classes[$token->getName()]['methods'])) { 177 for ($i = $token->getLine(); 178 $i <= $token->getEndLine(); 179 $i++) { 180 self::$ignoredLines[$filename][$i] = TRUE; 181 } 182 } else { 183 $firstMethod = array_shift( 184 $classes[$token->getName()]['methods'] 185 ); 186 187 do { 188 $lastMethod = array_pop( 189 $classes[$token->getName()]['methods'] 190 ); 191 } while ($lastMethod !== NULL && substr($lastMethod['signature'], 0, 18) == 'anonymous function'); 192 193 if ($lastMethod === NULL) { 194 $lastMethod = $firstMethod; 195 } 196 197 for ($i = $token->getLine(); 198 $i < $firstMethod['startLine']; 199 $i++) { 200 self::$ignoredLines[$filename][$i] = TRUE; 201 } 202 203 for ($i = $token->getEndLine(); 204 $i > $lastMethod['endLine']; 205 $i--) { 206 self::$ignoredLines[$filename][$i] = TRUE; 207 } 208 } 209 } 210 } 211 break; 212 213 case 'PHP_Token_NAMESPACE': { 214 self::$ignoredLines[$filename][$token->getEndLine()] = TRUE; 215 } // Intentional fallthrough 216 case 'PHP_Token_OPEN_TAG': 217 case 'PHP_Token_CLOSE_TAG': 218 case 'PHP_Token_USE': { 219 self::$ignoredLines[$filename][$token->getLine()] = TRUE; 220 } 221 break; 222 } 223 224 if ($ignore) { 225 self::$ignoredLines[$filename][$token->getLine()] = TRUE; 226 227 if ($stop) { 228 $ignore = FALSE; 229 $stop = FALSE; 230 } 231 } 232 } 233 } 234 235 return self::$ignoredLines[$filename]; 236 } 237 238 /** 239 * @param float $a 240 * @param float $b 241 * @return float ($a / $b) * 100 242 */ 243 public static function percent($a, $b, $asString = FALSE, $fixedWidth = FALSE) 244 { 245 if ($asString && $b == 0) { 246 return ''; 247 } 248 249 if ($b > 0) { 250 $percent = ($a / $b) * 100; 251 } else { 252 $percent = 100; 253 } 254 255 if ($asString) { 256 if ($fixedWidth) { 257 return sprintf('%6.2F%%', $percent); 258 } 259 260 return sprintf('%01.2F%%', $percent); 261 } else { 262 return $percent; 263 } 264 } 265 }