/** * Takes an instance of an object (usually a Collection object) containing test * instances. Introspects the test subject classes to extract cyclomatic complexity data. * * @param object $report Instance of Report which is calling apply. * @param array $tests The test to apply this filter on * @param array $options Not used. * @return object|void Returns the instance of `$tests`. */ public static function apply($report, $tests, array $options = array()) { $results = array(); foreach ($tests->invoke('subject') as $class) { $results[$class] = array(); if (!($methods = Inspector::methods($class, 'ranges', array('public' => false)))) { continue; } foreach ($methods as $method => $lines) { $lines = Inspector::lines($class, $lines); $branches = Parser::tokenize(join("\n", (array) $lines), array('include' => static::$_include)); $results[$class][$method] = count($branches) + 1; $report->collect(__CLASS__, $results); } } return $tests; }
protected function _display($file) { if (!($matches = Parser::tokenize(file_get_contents($file)))) { $this->stop(1, 'no matches'); } if (!$this->show) { $this->out($file . ':'); } $rows = array(array('', 'ID', 'LINE', 'TYPE', 'TEXT')); foreach ($matches as $match) { if (($id = substr(sha1($file . $match['line']), 0, 4)) == $this->show) { $this->stop(0, $file); } $rows[] = array('', $id, $match['line'], $match['type'], $match['text']); } if (!$this->show) { $this->out($this->columns($rows)); $this->hr(); $this->nl(); } }
/** * Gets the static and dynamic dependencies for a class or group of classes. * * @param mixed $classes Either a string specifying a class, or a numerically indexed array * of classes * @param array $options * @return array An array of the static and dynamic class dependencies * @todo Document valid options */ public static function dependencies($classes, array $options = array()) { $defaults = array('type' => null); $options += $defaults; $static = $dynamic = array(); $trim = function ($c) { return trim(trim($c, '\\')); }; $join = function ($i) { return join('', $i); }; foreach ((array) $classes as $class) { $data = explode("\n", file_get_contents(Libraries::path($class))); $data = "<?php \n" . join("\n", preg_grep('/^\\s*use /', $data)) . "\n ?>"; $classes = array_map($join, Parser::find($data, 'use *;', array('return' => 'content', 'lineBreaks' => true, 'startOfLine' => true, 'capture' => array('T_STRING', 'T_NS_SEPARATOR')))); if ($classes) { $static = array_unique(array_merge($static, array_map($trim, $classes))); } $classes = static::info($class . '::$_classes', array('value')); if (isset($classes['value'])) { $dynamic = array_merge($dynamic, array_map($trim, array_values($classes['value']))); } } if (empty($options['type'])) { return array_unique(array_merge($static, $dynamic)); } $type = $options['type']; return isset(${$type}) ? ${$type} : null; }
/** * Finds a pattern in a block of code. * * @param string $code * @param string $pattern * @param array $options The list of options to be used when parsing / matching `$code`: * - 'ignore': An array of token names to ignore while parsing, defaults to * `array('T_WHITESPACE')` * - 'lineBreaks': If true, all tokens in a single pattern match must appear on the * same line of code, defaults to false * - 'startOfLine': If true, the pattern must match starting with the beginning of * the line of code to be matched, defaults to false * @return array */ public static function find($code, $pattern, $options = array()) { $defaults = array('all' => true, 'capture' => array(), 'ignore' => array('T_WHITESPACE'), 'return' => true, 'lineBreaks' => false, 'startOfLine' => false); $options += $defaults; $results = array(); $matches = array(); $patternMatch = array(); $ret = $options['return']; $tokens = new Collection(array('items' => static::tokenize($code, $options))); $pattern = new Collection(array('items' => static::tokenize($pattern, $options))); $breaks = function ($token) use(&$tokens, &$matches, &$patternMatch, $options) { if (!$options['lineBreaks']) { return true; } if (empty($patternMatch) && !$options['startOfLine']) { return true; } if (empty($patternMatch)) { $prev = $tokens->prev(); $tokens->next(); } else { $prev = reset($patternMatch); } if (empty($patternMatch) && $options['startOfLine']) { return $token['line'] > $prev['line']; } return $token['line'] == $prev['line']; }; $capture = function ($token) use(&$matches, &$patternMatch, $tokens, $breaks, $options) { if (is_null($token)) { $matches = $patternMatch = array(); return false; } if (empty($patternMatch)) { $prev = $tokens->prev(); $tokens->next(); if ($options['startOfLine'] && $token['line'] == $prev['line']) { $patternMatch = $matches = array(); return false; } } $patternMatch[] = $token; if (empty($options['capture']) || !in_array($token['name'], $options['capture'])) { return true; } if (!$breaks($token)) { $matches = array(); return true; } $matches[] = $token; return true; }; $executors = array('*' => function (&$tokens, &$pattern) use($options, $capture) { $closing = $pattern->next(); $tokens->prev(); while (($t = $tokens->next()) && !Parser::matchToken($closing, $t)) { $capture($t); } $pattern->next(); }); $tokens->rewind(); $pattern->rewind(); while ($tokens->valid()) { if (!$pattern->valid()) { $pattern->rewind(); if (!empty($matches)) { $results[] = array_map(function ($i) use($ret) { return isset($i[$ret]) ? $i[$ret] : $i; }, $matches); } $capture(null); } $p = $pattern->current(); $t = $tokens->current(); switch (true) { case static::matchToken($p, $t): $capture($t) ? $pattern->next() : $pattern->rewind(); break; case isset($executors[$p['name']]): $exec = $executors[$p['name']]; $exec($tokens, $pattern); break; default: $capture(null); $pattern->rewind(); break; } $tokens->next(); } return $results; }
public function testParserGuessesLineBleed() { $code = <<<EOD if (false) { \treturn true; } EOD; $tokens = Parser::tokenize($code); $this->assertIdentical('}', $tokens[13]['content']); $this->assertIdentical(3, $tokens[13]['line']); }
/** * Adds more token information than the base lithium tokenizer such as name, * parent, and children. * * @param string $code Source code to be tokenized. * @param array $options Options consists of: * -'wrap': Boolean indicating whether or not to wrap the supplied * code in PHP tags. * -'ignore': An array containing PHP language tokens to ignore. * -'include': If supplied, an array of the only language tokens * to include in the output. * @return array An array of extracted information from the supplied source code: * - lineCache: token ids indexed by line number * - typeCache: token ids indexed by token type * - meta: parsing information (level, etc.) indexed by token id * - relationships: parent and child relations (token ids) indexed by token id */ public static function tokenize($code, array $options = array()) { $options += array('wrap' => true); $tokens = static::_tokenize(parent::tokenize($code, $options)); static::$_bracketsChecksum = 0; $curParent = -1; $brackets = $curlyBrackets = $squareBrackets = 0; $level = $needNestUp = $nestLine = $nestLevel = 0; $nestLog = $lineCache = $typeCache = array(); $inString = false; $inPhp = $options['wrap'] ? true : false; foreach ($tokens as $tokenId => $token) { $isString = $token['id'] === T_CONSTANT_ENCAPSED_STRING || $token['id'] === T_ENCAPSED_AND_WHITESPACE; $lineCache[$token['line']][] = $tokenId; if ($isString) { $carriageReturn = substr_count($token['content'], "\n"); for ($i = 1; $i <= $carriageReturn; $i++) { $lineCache[$token['line'] + $i][] = $tokenId; } } $typeCache[$token['id']][] = $tokenId; if ($token['id'] === T_CLOSE_TAG) { $needNestUp = 0; $nestLevel = 0; $inPhp = false; } if ($token['id'] === T_OPEN_TAG) { $inPhp = true; } if (!$inPhp) { continue; } if ($token['id'] === T_END_DOUBLE_QUOTE || $token['id'] === T_END_HEREDOC) { $inString = false; } if ($needNestUp > 0) { $status = $nestLog[$needNestUp]; if (!$status['founded'] && in_array($token['content'], $status['nestOn'])) { $nestLog[$needNestUp]['founded'] = true; } if ($token['line'] > $nestLine && $status['founded'] && !$status['applied']) { $nestLevel++; $nestLog[$needNestUp]['applied'] = true; } } $tokens[$tokenId] = static::_checksum($tokens[$tokenId], $isString || $inString); $tokens[$tokenId]['level'] = $level; $tokens[$tokenId]['parent'] = $curParent; $tokens[$tokenId]['children'] = array(); if (isset($tokens[$curParent])) { $tokens[$curParent]['children'][] = $tokenId; } while (($parent = static::_isEndOfParent($tokenId, $curParent, $tokens)) !== false) { if (!$inString && static::$_parentTokens[$tokens[$curParent]['id']]['nestOn']) { $needNestUp === 0 ?: $needNestUp--; $nestLevel = $tokens[$curParent]['nestLevel']; } $level--; $curParent = $parent; } $tokens[$tokenId]['nestLevel'] = $inString ? null : $nestLevel; $tokens[$tokenId]['isString'] = $isString; if ($token['id'] === T_START_DOUBLE_QUOTE || $token['id'] === T_START_HEREDOC) { $inString = true; } if (static::_isParent($tokenId, $tokens)) { $tokens[$tokenId]['parent'] = $curParent; if (!$inString && ($nestOn = static::$_parentTokens[$token['id']]['nestOn'])) { $nestLine = $token['line']; $nestLog[++$needNestUp] = array('nestOn' => $nestOn, 'founded' => $nestOn === true ? true : false, 'applied' => false); } $curParent = $tokenId; $level++; } } if ($level !== 0 || $squareBrackets !== 0 || $curlyBrackets !== 0 || $brackets !== 0) { $smallTokens = array_slice($tokens, 0, 20); $exception = new ParserException('A parse error has been encountered.'); $exception->parserData = compact('level', 'curlyBrackets', 'brackets', 'tokens'); throw $exception; } return compact('tokens', 'lineCache', 'typeCache'); }
public function testFindingTokenPatterns() { $code = file_get_contents(\lithium\core\Libraries::path('lithium\\analysis\\Parser')); $expected = array('tokenize', 'matchToken', '_prepareMatchParams', 'token'); $results = array_values(array_unique(array_map(function ($i) { return $i[0]; }, Parser::find($code, 'static::_(*)', array('capture' => array('T_STRING'), 'return' => 'content'))))); $this->assertEqual($expected, $results); $expected = array('\\ReflectionClass', '\\lithium\\core\\Libraries', '\\lithium\\util\\Collection', '\\lithium\\util\\Validator', '\\lithium\\util\\Set'); $results = array_map(function ($i) { return join('', $i); }, $results = Parser::find($code, 'use *;', array('return' => 'content', 'lineBreaks' => true, 'startOfLine' => true, 'capture' => array('T_STRING', 'T_NS_SEPARATOR')))); $this->assertEqual($expected, $results); $code = 'function test($options) { return function($foo) use ($options) {'; $code .= ' ClassName::method($options); ' . "\n" . ' $foo->method($options); }; }'; list($results) = Parser::find($code, '_::_(', array('capture' => array('T_STRING'), 'return' => 'content')); $expected = array('ClassName', 'method'); $this->assertEqual($expected, $results); }
public function testParserGuessesLineBleedWithNonWhitespace() { $code = <<<EOD if (false) { \t// hello world } EOD; $tokens = Parser::tokenize($code); $this->assertIdentical('}', $tokens[9]['content']); $this->assertIdentical(3, $tokens[9]['line']); }