public function testParser()
 {
     if (!PhutilXHPASTBinary::isAvailable()) {
         try {
             PhutilXHPASTBinary::build();
         } catch (Exception $ex) {
             $this->assertSkipped(pht('%s is not built or not up to date.', 'xhpast'));
         }
     }
     $dir = dirname(__FILE__) . '/data/';
     foreach (Filesystem::listDirectory($dir) as $file) {
         if (preg_match('/\\.test$/', $file)) {
             $this->executeParserTest($file, Filesystem::readFile($dir . $file));
         }
     }
 }
 public function scan()
 {
     $output = $this->output;
     if (!\PhutilXHPASTBinary::isAvailable()) {
         $output->writeln(\PhutilXHPASTBinary::getBuildInstructions(), $output::OUTPUT_NORMAL);
         exit;
     }
     $base_path = $this->basePath;
     $php_files = $this->allFiles($base_path);
     $total_files = count($php_files);
     $this->output->writeln("{$total_files} files to scan");
     /** @type \Generator $split */
     $split = $this->chunk($php_files, ceil($total_files / $this->workers));
     foreach ($split as $chunk) {
         $this->forkProcess($chunk);
     }
     foreach ($this->processes as $pID) {
         pcntl_waitpid($pID, $status);
     }
 }
 public function getHighlightFuture($language, $source)
 {
     if ($language === null) {
         $language = PhutilLanguageGuesser::guessLanguage($source);
     }
     $have_pygments = !empty($this->config['pygments.enabled']);
     if ($language == 'php' && PhutilXHPASTBinary::isAvailable()) {
         return id(new PhutilXHPASTSyntaxHighlighter())->getHighlightFuture($source);
     }
     if ($language == 'console') {
         return id(new PhutilConsoleSyntaxHighlighter())->getHighlightFuture($source);
     }
     if ($language == 'diviner' || $language == 'remarkup') {
         return id(new PhutilDivinerSyntaxHighlighter())->getHighlightFuture($source);
     }
     if ($language == 'rainbow') {
         return id(new PhutilRainbowSyntaxHighlighter())->getHighlightFuture($source);
     }
     if ($language == 'php') {
         return id(new PhutilLexerSyntaxHighlighter())->setConfig('lexer', new PhutilPHPFragmentLexer())->setConfig('language', 'php')->getHighlightFuture($source);
     }
     if ($language == 'py') {
         return id(new PhutilLexerSyntaxHighlighter())->setConfig('lexer', new PhutilPythonFragmentLexer())->setConfig('language', 'py')->getHighlightFuture($source);
     }
     if ($language == 'json') {
         return id(new PhutilLexerSyntaxHighlighter())->setConfig('lexer', new PhutilJSONFragmentLexer())->getHighlightFuture($source);
     }
     if ($language == 'invisible') {
         return id(new PhutilInvisibleSyntaxHighlighter())->getHighlightFuture($source);
     }
     // Don't invoke Pygments for plain text, since it's expensive and has
     // no effect.
     if ($language !== 'text' && $language !== 'txt') {
         if ($have_pygments) {
             return id(new PhutilPygmentsSyntaxHighlighter())->setConfig('language', $language)->getHighlightFuture($source);
         }
     }
     return id(new PhutilDefaultSyntaxHighlighter())->getHighlightFuture($source);
 }
 /**
  * Analyze the library, generating the file and symbol maps.
  *
  * @return void
  */
 private function analyzeLibrary()
 {
     // Identify all the ".php" source files in the library.
     $this->log(pht('Finding source files...'));
     $source_map = $this->loadSourceFileMap();
     $this->log(pht('Found %s files.', new PhutilNumber(count($source_map))));
     // Load the symbol cache with existing parsed symbols. This allows us
     // to remap libraries quickly by analyzing only changed files.
     $this->log(pht('Loading symbol cache...'));
     $symbol_cache = $this->loadSymbolCache();
     // If the XHPAST binary is not up-to-date, build it now. Otherwise,
     // `phutil_symbols.php` will attempt to build the binary and will fail
     // miserably because it will be trying to build the same file multiple
     // times in parallel.
     if (!PhutilXHPASTBinary::isAvailable()) {
         PhutilXHPASTBinary::build();
     }
     // Build out the symbol analysis for all the files in the library. For
     // each file, check if it's in cache. If we miss in the cache, do a fresh
     // analysis.
     $symbol_map = array();
     $futures = array();
     foreach ($source_map as $file => $hash) {
         if (!empty($symbol_cache[$hash])) {
             $symbol_map[$file] = $symbol_cache[$hash];
             continue;
         }
         $futures[$file] = $this->buildSymbolAnalysisFuture($file);
     }
     $this->log(pht('Found %s files in cache.', new PhutilNumber(count($symbol_map))));
     // Run the analyzer on any files which need analysis.
     if ($futures) {
         $limit = $this->subprocessLimit;
         $count = number_format(count($futures));
         $this->log(pht('Analyzing %d files with %d subprocesses...', $count, $limit));
         $progress = new PhutilConsoleProgressBar();
         if ($this->quiet) {
             $progress->setQuiet(true);
         }
         $progress->setTotal(count($futures));
         $futures = id(new FutureIterator($futures))->limit($limit);
         foreach ($futures as $file => $future) {
             $result = $future->resolveJSON();
             if (empty($result['error'])) {
                 $symbol_map[$file] = $result;
             } else {
                 $progress->done(false);
                 throw new XHPASTSyntaxErrorException($result['line'], $file . ': ' . $result['error']);
             }
             $progress->update(1);
         }
         $progress->done();
     }
     $this->fileSymbolMap = $symbol_map;
     // We're done building the cache, so write it out immediately. Note that
     // we've only retained entries for files we found, so this implicitly cleans
     // out old cache entries.
     $this->writeSymbolCache($symbol_map, $source_map);
     // Our map is up to date, so either show it on stdout or write it to disk.
     $this->log(pht('Building library map...'));
     $this->librarySymbolMap = $this->buildLibraryMap($symbol_map);
 }