/** * Processes a single file. * * @param string $filename * @param bool $countTests */ public function countFile($filename, $countTests) { if ($countTests) { $this->preProcessFile($filename); } $buffer = file_get_contents($filename); $this->collector->incrementLines(substr_count($buffer, "\n")); $tokens = token_get_all($buffer); $numTokens = count($tokens); unset($buffer); $this->collector->addFile($filename); $blocks = []; $currentBlock = false; $namespace = false; $className = null; $functionName = null; $testClass = false; $this->collector->currentClassReset(); $isInMethod = false; for ($i = 0; $i < $numTokens; $i++) { if (is_string($tokens[$i])) { $token = trim($tokens[$i]); if ($token == ';') { if ($className !== null && !$testClass) { $this->collector->currentClassIncrementLines(); if ($functionName !== null) { $this->collector->currentMethodIncrementLines(); } } elseif ($functionName !== null) { $this->collector->incrementFunctionLines(); } $this->collector->incrementLogicalLines(); } elseif ($token == '?' && !$testClass) { if ($className !== null) { $this->collector->currentClassIncrementComplexity(); $this->collector->currentMethodIncrementComplexity(); } $this->collector->incrementComplexity(); } elseif ($token == '{') { if ($currentBlock == T_CLASS) { $block = $className; } elseif ($currentBlock == T_FUNCTION) { $block = $functionName; } else { $block = false; } array_push($blocks, $block); $currentBlock = false; } elseif ($token == '}') { $block = array_pop($blocks); if ($block !== false && $block !== null) { if ($block == $functionName) { $functionName = null; if ($isInMethod) { $this->collector->currentMethodStop(); $isInMethod = false; } } elseif ($block == $className) { $className = null; $testClass = false; $this->collector->currentClassReset(); } } } continue; } list($token, $value) = $tokens[$i]; switch ($token) { case T_NAMESPACE: $namespace = $this->getNamespaceName($tokens, $i); $this->collector->addNamespace($namespace); break; case T_CLASS: case T_INTERFACE: case T_TRAIT: if (!$this->isClassDeclaration($tokens, $i)) { continue; } $this->collector->currentClassReset(); $this->collector->currentClassIncrementComplexity(); $className = $this->getClassName($namespace, $tokens, $i); $currentBlock = T_CLASS; if ($token == T_TRAIT) { $this->collector->incrementTraits(); } elseif ($token == T_INTERFACE) { $this->collector->incrementInterfaces(); } else { if ($countTests && $this->isTestClass($className)) { $testClass = true; $this->collector->incrementTestClasses(); } else { if (isset($tokens[$i - 2]) && is_array($tokens[$i - 2]) && $tokens[$i - 2][0] == T_ABSTRACT) { $this->collector->incrementAbstractClasses(); } else { $this->collector->incrementConcreteClasses(); } } } break; case T_FUNCTION: $prev = $this->getPreviousNonWhitespaceTokenPos($tokens, $i); if ($tokens[$prev][0] === T_USE) { continue; } $currentBlock = T_FUNCTION; $next = $this->getNextNonWhitespaceTokenPos($tokens, $i); if (!is_array($tokens[$next]) && $tokens[$next] == '&') { $next = $this->getNextNonWhitespaceTokenPos($tokens, $next); } if (is_array($tokens[$next]) && $tokens[$next][0] == T_STRING) { $functionName = $tokens[$next][1]; } else { $currentBlock = 'anonymous function'; $functionName = 'anonymous function'; $this->collector->incrementAnonymousFunctions(); } if ($currentBlock == T_FUNCTION) { if ($className === null && $functionName != 'anonymous function') { $this->collector->incrementNamedFunctions(); } else { $static = false; $visibility = T_PUBLIC; for ($j = $i; $j > 0; $j--) { if (is_string($tokens[$j])) { if ($tokens[$j] == '{' || $tokens[$j] == '}' || $tokens[$j] == ';') { break; } continue; } if (isset($tokens[$j][0])) { switch ($tokens[$j][0]) { case T_PRIVATE: $visibility = T_PRIVATE; break; case T_PROTECTED: $visibility = T_PROTECTED; break; case T_STATIC: $static = true; break; } } } if ($testClass && $this->isTestMethod($functionName, $visibility, $static, $tokens, $i)) { $this->collector->incrementTestMethods(); } elseif (!$testClass) { $isInMethod = true; $this->collector->currentMethodStart(); if (!$static) { $this->collector->incrementNonStaticMethods(); } else { $this->collector->incrementStaticMethods(); } if ($visibility == T_PUBLIC) { $this->collector->incrementPublicMethods(); } else { $this->collector->incrementNonPublicMethods(); } } } } break; case T_CURLY_OPEN: $currentBlock = T_CURLY_OPEN; array_push($blocks, $currentBlock); break; case T_DOLLAR_OPEN_CURLY_BRACES: $currentBlock = T_DOLLAR_OPEN_CURLY_BRACES; array_push($blocks, $currentBlock); break; case T_IF: case T_ELSEIF: case T_FOR: case T_FOREACH: case T_WHILE: case T_CASE: case T_CATCH: case T_BOOLEAN_AND: case T_LOGICAL_AND: case T_BOOLEAN_OR: case T_LOGICAL_OR: if (!$testClass) { if ($isInMethod) { $this->collector->currentClassIncrementComplexity(); $this->collector->currentMethodIncrementComplexity(); } $this->collector->incrementComplexity(); } break; case T_COMMENT: case T_DOC_COMMENT: // We want to count all intermediate lines before the token ends // But sometimes a new token starts after a newline, we don't want to count that. // That happend with /* */ and /** */, but not with // since it'll end at the end $this->collector->incrementCommentLines(substr_count(rtrim($value, "\n"), "\n") + 1); break; case T_CONST: $this->collector->incrementClassConstants(); break; case T_STRING: if ($value == 'define') { $this->collector->incrementGlobalConstants(); $j = $i + 1; while (isset($tokens[$j]) && $tokens[$j] != ';') { if (is_array($tokens[$j]) && $tokens[$j][0] == T_CONSTANT_ENCAPSED_STRING) { $this->collector->addConstant(str_replace('\'', '', $tokens[$j][1])); break; } $j++; } } else { $this->collector->addPossibleConstantAccesses($value); } break; case T_DOUBLE_COLON: case T_OBJECT_OPERATOR: $n = $this->getNextNonWhitespaceTokenPos($tokens, $i); $nn = $this->getNextNonWhitespaceTokenPos($tokens, $n); if ($n && $nn && isset($tokens[$n][0]) && ($tokens[$n][0] == T_STRING || $tokens[$n][0] == T_VARIABLE) && $tokens[$nn] == '(') { if ($token == T_DOUBLE_COLON) { $this->collector->incrementStaticMethodCalls(); } else { $this->collector->incrementNonStaticMethodCalls(); } } else { if ($token == T_DOUBLE_COLON && $tokens[$n][0] == T_VARIABLE) { $this->collector->incrementStaticAttributeAccesses(); } elseif ($token == T_OBJECT_OPERATOR) { $this->collector->incrementNonStaticAttributeAccesses(); } } break; case T_GLOBAL: $this->collector->incrementGlobalVariableAccesses(); break; case T_VARIABLE: if ($value == '$GLOBALS') { $this->collector->incrementGlobalVariableAccesses(); } elseif (isset($this->superGlobals[$value])) { $this->collector->incrementSuperGlobalVariableAccesses(); } break; } } }