/** * Reads and parses test data from a specified file. * * This method reads and parses test data from a file. The file is expected * to have the following structure * * ``` * <?php * // PHP code goes here. * ~~~~~~~~~~ * { * // JSON dictionary containing expected results from testing method. * } * ``` * * @param string The path to the test file. * @return pair<XHPASTTree, map> The first element of the pair is the * `XHPASTTree` contained within the test file. * The second element of the pair is the * "expect" data. */ private function readTestData($file) { $contents = Filesystem::readFile($file); $contents = preg_split('/^~{10}$/m', $contents); if (count($contents) < 2) { throw new Exception(pht("Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect) = $contents; $tree = XHPASTTree::newFromData($data); $expect = phutil_json_decode($expect); return array($tree, $expect); }
public static function evalStaticString($string) { $string = '<?php ' . rtrim($string, ';') . ';'; $tree = XHPASTTree::newFromData($string); $statements = $tree->getRootNode()->selectDescendantsOfType('n_STATEMENT'); if (count($statements) != 1) { throw new Exception("String does not parse into exactly one statement!"); } // Return the first one, trying to use reset() with iterators ends in tears. foreach ($statements as $statement) { return $statement->evalStatic(); } }
as it's relatively stable and performance is currently awful (500ms+ for moderately large files). EOHELP ); $args->parseStandardArguments(); $args->parse(array(array('name' => 'all', 'help' => pht('Report all symbols, including built-ins and declared externals.')), array('name' => 'ugly', 'help' => pht('Do not prettify JSON output.')), array('name' => 'path', 'wildcard' => true, 'help' => pht('PHP Source file to analyze.')))); $paths = $args->getArg('path'); if (count($paths) !== 1) { throw new Exception(pht('Specify exactly one path!')); } $path = Filesystem::resolvePath(head($paths)); $show_all = $args->getArg('all'); $source_code = Filesystem::readFile($path); try { $tree = XHPASTTree::newFromData($source_code); } catch (XHPASTSyntaxErrorException $ex) { $result = array('error' => $ex->getMessage(), 'line' => $ex->getErrorLine(), 'file' => $path); $json = new PhutilJSON(); echo $json->encodeFormatted($result); exit(0); } $root = $tree->getRootNode(); $root->buildSelectCache(); // -( Unsupported Constructs )------------------------------------------------ $namespaces = $root->selectDescendantsOfType('n_NAMESPACE'); foreach ($namespaces as $namespace) { phutil_fail_on_unsupported_feature($namespace, $path, pht('namespaces')); } $uses = $root->selectDescendantsOfType('n_USE'); foreach ($namespaces as $namespace) {
private function applyXHPHighlight($source) { // We perform two passes here: one using the AST to find symbols we care // about -- particularly, class names and function names. These are used // in the crossreference stuff to link into Diffusion. After we've done our // AST pass, we do a followup pass on the token stream to catch all the // simple stuff like strings and comments. $scrub = false; if (strpos($source, '<?') === false) { $source = "<?php\n" . $source . "\n"; $scrub = true; } $tree = XHPASTTree::newFromData($source); $root = $tree->getRootNode(); $tokens = $root->getTokens(); $interesting_symbols = $this->findInterestingSymbols($root); $out = array(); foreach ($tokens as $key => $token) { $value = phutil_escape_html($token->getValue()); $class = null; $multi = false; $attrs = ''; if (isset($interesting_symbols[$key])) { $sym = $interesting_symbols[$key]; $class = $sym[0]; if (isset($sym['context'])) { $attrs = $attrs . ' data-symbol-context="' . $sym['context'] . '"'; } if (isset($sym['symbol'])) { $attrs = $attrs . ' data-symbol-name="' . $sym['symbol'] . '"'; } } else { switch ($token->getTypeName()) { case 'T_WHITESPACE': break; case 'T_DOC_COMMENT': $class = 'dc'; $multi = true; break; case 'T_COMMENT': $class = 'c'; $multi = true; break; case 'T_CONSTANT_ENCAPSED_STRING': case 'T_ENCAPSED_AND_WHITESPACE': case 'T_INLINE_HTML': $class = 's'; $multi = true; break; case 'T_VARIABLE': $class = 'nv'; break; case 'T_OPEN_TAG': case 'T_OPEN_TAG_WITH_ECHO': case 'T_CLOSE_TAG': $class = 'o'; break; case 'T_LNUMBER': case 'T_DNUMBER': $class = 'm'; break; case 'T_STRING': static $magic = array('true' => true, 'false' => true, 'null' => true); if (isset($magic[$value])) { $class = 'k'; break; } $class = 'nx'; break; default: $class = 'k'; break; } } if ($class) { $l = '<span class="' . $class . '"' . $attrs . '>'; $r = '</span>'; $value = $l . $value . $r; if ($multi) { // If the token may have multiple lines in it, make sure each // <span> crosses no more than one line so the lines can be put // in a table, etc., later. $value = str_replace("\n", $r . "\n" . $l, $value); } $out[] = $value; } else { $out[] = $value; } } if ($scrub) { array_shift($out); } return rtrim(implode('', $out)); }