/** * Calculate cyclomatic complexity number * * We can calculate ccn in two ways (we choose the second): * * 1. Cyclomatic complexity (CC) = E - N + 2P * Where: * P = number of disconnected parts of the flow graph (e.g. a calling program and a subroutine) * E = number of edges (transfers of control) * N = number of nodes (sequential group of statements containing only one transfer of control) * * 2. CC = Number of each decision point * * @param string $filename * @return Result */ public function calculate($filename) { $info = new Result(); $tokens = $this->tokenizer->tokenize($filename); $ccn = 0; foreach ($tokens as $token) { switch ($token->getType()) { case T_IF: case T_ELSEIF: case T_FOREACH: case T_FOR: case T_WHILE: case T_DO: case T_BOOLEAN_AND: case T_LOGICAL_AND: case T_BOOLEAN_OR: case T_LOGICAL_OR: case T_CASE: case T_DEFAULT: case T_CATCH: case T_CONTINUE: $ccn++; break; } } $info->setCyclomaticComplexityNumber(max(1, $ccn)); return $info; }
/** * Extract infos from file * * @param $filename * @return Result */ public function extract($filename) { $result = new Result(); $tokens = $this->tokenizer->tokenize($filename); $nameResolver = new NameResolver(); // default current values $class = $interface = $function = $method = null; // $mapOfAliases = array(); $len = sizeof($tokens, COUNT_NORMAL); for ($n = 0; $n < $len; $n++) { $token = $tokens[$n]; switch ($token->getType()) { case T_USE: $alias = $this->extractors->alias->extract($n, $tokens); if (null !== $alias->name && null !== $alias->alias) { $nameResolver->pushAlias($alias); } break; case T_NAMESPACE: $namespace = '\\' . $this->searcher->getFollowingName($n, $tokens); $this->extractors->class->setNamespace($namespace); $this->extractors->interface->setNamespace($namespace); break; case T_INTERFACE: $class = $this->extractors->interface->extract($n, $tokens); $class->setNameResolver($nameResolver); // push class AND in global AND in local class map $this->result->pushClass($class); $result->pushClass($class); break; case T_EXTENDS: $i = $n; $parent = $this->searcher->getFollowingName($i, $tokens); $class->setParent(trim($parent)); break; case T_CLASS: $class = $this->extractors->class->extract($n, $tokens); $class->setNameResolver($nameResolver); // push class AND in global AND in local class map $this->result->pushClass($class); $result->pushClass($class); break; case T_FUNCTION: if ($class) { // avoid closure $next = $tokens[$n + 1]; if (T_WHITESPACE != $next->getType()) { continue; } $method = $this->extractors->method->extract($n, $tokens); $method->setNameResolver($nameResolver); $class->pushMethod($method); } break; } } return $result; }
public function onParseTestedFilesEnd(UnitsResultEvent $event) { $tokenizer = new Tokenizer(); $units = $event->getUnits(); foreach ($units->all() as $unit) { $testedFiles = $unit->getTestedFiles(); foreach ($testedFiles as $filename) { $tokens = new \Hal\MutaTesting\Token\TokenCollection($tokenizer->tokenize($filename)); array_push($this->allTokens, $tokens); } } }
/** * Inventories tokens * * @param string $filename * @return $this */ private function inventory($filename) { $this->operators = $this->operands = array(); $tokens = $this->tokenizer->tokenize($filename); foreach ($tokens as $token) { if ($this->tokenType->isOperator($token)) { $this->operators[] = $token; } else { if ($this->tokenType->isOperand($token)) { $this->operands[] = $token; } } } return $this; }
public function testWorkingWithShortOpenTagsIsEquivalentToLongTags() { $content1 = '<?php echo "ok";'; $content2 = '<? echo "ok";'; $filename1 = tempnam(sys_get_temp_dir(), 'phpmetrics-unit'); $filename2 = tempnam(sys_get_temp_dir(), 'phpmetrics-unit'); file_put_contents($filename1, $content1); file_put_contents($filename2, $content2); $tokenizer = new Tokenizer(); $r1 = $tokenizer->tokenize($filename1); $r2 = $tokenizer->tokenize($filename2); $this->assertEquals($r1, $r2); unlink($filename1); unlink($filename2); }
public function factory($fileOrigin, $testFile) { $mutation = new Mutation(); $tokenizer = new Tokenizer(); $mutation->setTokens($tokenizer->tokenize($fileOrigin))->setSourceFile($fileOrigin)->setTestFile($testFile); foreach ($mutation->getTokens() as $index => $token) { if ($this->mutaterFactory->isMutable($token)) { $mutater = $this->mutaterFactory->factory($token); $mutated = $mutater->mutate($mutation, $index); if ($this->specification->isSatisfedBy($mutated, $index)) { $mutation->addMutation($mutated); } } } return $mutation; }
/** * Calculates Myer's interval * * Cyclomatic complexity : Cyclomatic complexity + L * where L is the number of logical operators * * @param string $filename * @return Result */ public function calculate($filename) { $mcCabe = new McCabe($this->tokenizer); $result = new Result(); $tokens = $this->tokenizer->tokenize($filename); // Cyclomatic complexity $cc = $mcCabe->calculate($filename); // Number of operator $L = 0; $logicalOperators = array(T_BOOLEAN_AND => T_BOOLEAN_AND, T_LOGICAL_AND => T_LOGICAL_AND, T_BOOLEAN_OR => T_BOOLEAN_OR, T_LOGICAL_OR => T_LOGICAL_OR); foreach ($tokens as $token) { if (isset($logicalOperators[$token->getType()])) { $L++; } } $result->setNumberOfOperators($L)->setMcCabe($cc); return $result; }
/** * Calculate cyclomatic complexity number * * We can calculate ccn in two ways (we choose the second): * * 1. Cyclomatic complexity (CC) = E - N + 2P * Where: * P = number of disconnected parts of the flow graph (e.g. a calling program and a subroutine) * E = number of edges (transfers of control) * N = number of nodes (sequential group of statements containing only one transfer of control) * * 2. CC = Number of each decision point * * @param string $filename * @return Result */ public function calculate($filename) { $info = new Result(); $tokens = $this->tokenizer->tokenize($filename); $ccn = 1; // default path foreach ($tokens as $token) { switch ($token->getType()) { case T_IF: case T_ELSEIF: case T_FOREACH: case T_FOR: case T_WHILE: case T_DO: case T_BOOLEAN_AND: case T_LOGICAL_AND: case T_BOOLEAN_OR: case T_LOGICAL_OR: case T_SPACESHIP: case T_CASE: case T_DEFAULT: case T_CATCH: case T_CONTINUE: $ccn++; break; case T_STRING: if ('?' == $token->getValue()) { $ccn = $ccn + 2; } break; case T_COALESCE: $ccn = $ccn + 2; break; } } $info->setCyclomaticComplexityNumber(max(1, $ccn)); return $info; }
/** * Calculates Lines of code * * @param string $filename * @return Result */ public function calculate($filename) { $info = new Result(); $tokens = $this->tokenizer->tokenize($filename); $content = file_get_contents($filename); $cloc = $lloc = 0; foreach ($tokens as $token) { switch ($token->getType()) { case T_STRING: if (';' == $token->getValue()) { $lloc++; } break; case T_COMMENT: $cloc++; break; case T_DOC_COMMENT: $cloc += count(preg_split('/\\r\\n|\\r|\\n/', $token->getValue())); break; } } $info->setLoc(count(preg_split('/\\r\\n|\\r|\\n/', $content)) - 1)->setCommentLoc($cloc)->setLogicalLoc($lloc); return $info; }
/** * Extract infos from file * * @param $filename * @return Result */ public function extract($filename) { $result = new Result(); $tokens = $this->tokenizer->tokenize($filename); $nameResolver = new NameResolver(); // default current values $class = $interface = $function = $namespace = $method = null; $len = sizeof($tokens, COUNT_NORMAL); $endAnonymous = 0; $mainContextClass = null; // class containing a anonymous class for ($n = 0; $n < $len; $n++) { if ($mainContextClass && $n > $endAnonymous) { // anonymous class is finished. We back to parent class // methods will be added to the main class now $class = $mainContextClass; $mainContextClass = null; } $token = $tokens[$n]; switch ($token->getType()) { case T_USE: $alias = $this->extractors->alias->extract($n, $tokens); if (null !== $alias->name && null !== $alias->alias) { $nameResolver->pushAlias($alias); } break; case T_NAMESPACE: $namespace = '\\' . $this->searcher->getFollowingName($n, $tokens); $this->extractors->class->setNamespace($namespace); $this->extractors->interface->setNamespace($namespace); break; case T_INTERFACE: $class = $this->extractors->interface->extract($n, $tokens); $class->setNameResolver($nameResolver); // push class AND in global AND in local class map $this->result->pushClass($class); $result->pushClass($class); break; case T_EXTENDS: $i = $n; $parent = $this->searcher->getFollowingName($i, $tokens); $class->setParent(trim($parent)); break; case T_IMPLEMENTS: $i = $n + 1; $contracts = $this->searcher->getUnder(array('{'), $i, $tokens); $contracts = explode(',', $contracts); $contracts = array_map('trim', $contracts); $class->setInterfaces($contracts); break; case T_CLASS: $c = $this->extractors->class->extract($n, $tokens); $c->setNameResolver($nameResolver); // push class AND in global AND in local class map $this->result->pushClass($c); $result->pushClass($c); // PHP 7 and inner classes if ($c instanceof ReflectedAnonymousClass) { // avoid to consider anonymous class as main class $p = $n; $endAnonymous = $this->searcher->getPositionOfClosingBrace($p, $tokens); $mainContextClass = $class; // add anonymous class in method if ($method) { $method->pushAnonymousClass($c); } } $class = $c; break; case T_FUNCTION: if ($class) { // avoid closure $next = $tokens[$n + 1]; if (T_WHITESPACE != $next->getType()) { continue; } $method = $this->extractors->method->extract($n, $tokens, $class); $method->setNamespace($namespace); $class->pushMethod($method); } break; } } return $result; }
/** * @inheritdoc */ public function execute(ResultCollection $collection, ResultCollection $aggregatedResults) { $files = $this->finder->find($this->path); if (0 == sizeof($files, COUNT_NORMAL)) { throw new \LogicException('No file found'); } $progress = new ProgressBar($this->output); $progress->start(sizeof($files, COUNT_NORMAL)); // tools $classMap = new ClassMap(); $tokenizer = new Tokenizer(); $syntaxChecker = new SyntaxChecker(); $fileAnalyzer = new FileAnalyzer($this->output, $this->withOOP, new Extractor(), new \Hal\Metrics\Complexity\Text\Halstead\Halstead(new \Hal\Component\Token\TokenType()), new \Hal\Metrics\Complexity\Text\Length\Loc(), new \Hal\Metrics\Design\Component\MaintainabilityIndex\MaintainabilityIndex(), new \Hal\Metrics\Complexity\Component\McCabe\McCabe(), new \Hal\Metrics\Complexity\Component\Myer\Myer(), $classMap); foreach ($files as $k => $filename) { $progress->advance(); // Integrity if (!$this->ignoreErrors && !$syntaxChecker->isCorrect($filename)) { $this->output->writeln(sprintf('<error>file %s is not valid and has been skipped</error>', $filename)); unset($files[$k]); continue; } // Analyze try { $tokens = $tokenizer->tokenize($filename); $resultSet = $fileAnalyzer->execute($filename, $tokens); } catch (NoTokenizableException $e) { $this->output->writeln(sprintf("<error>file %s has been skipped: \n%s</error>", $filename, $e->getMessage())); unset($files[$k]); continue; } catch (\Exception $e) { throw new \Exception(sprintf("a '%s' exception occured analyzing file %s\nMessage: %s", get_class($e), $filename, $e->getMessage()) . sprintf("\n------------\nStack:\n%s", $e->getTraceAsString()) . sprintf("\n------------\nDo not hesitate to report a bug: https://github.com/PhpMetrics/PhpMetrics/issues"), 0, $e->getPrevious()); } $collection->push($resultSet); } $progress->clear(); $progress->finish(); if ($this->withOOP) { // COUPLING (should be done after parsing files) $this->output->write(str_pad("\rAnalyzing coupling. This will take few minutes...", 80, " ")); $couplingAnalyzer = new CouplingAnalyzer($classMap, $collection); $couplingAnalyzer->execute($files); // LCOM (should be done after parsing files) $this->output->write(str_pad("\rLack of cohesion of method (lcom). This will take few minutes...", 80, " ")); $lcomAnalyzer = new LcomAnalyzer($classMap, $collection); $lcomAnalyzer->execute($files); // Card and Agresti (should be done after parsing files) $this->output->write(str_pad("\rAnalyzing System complexity. This will take few minutes...", 80, " ")); $lcomAnalyzer = new CardAndAgrestiAnalyzer($classMap, $collection); $lcomAnalyzer->execute($files); } }