parseFile() public static method

This first pass parses code and looks for the subset of issues that can be found without having to have an understanding of the entire code base.
public static parseFile ( CodeBase $code_base, string $file_path ) : Context
$code_base CodeBase The CodeBase represents state across the entire code base. This is a mutable object which is populated as we parse files
$file_path string The full path to a file we'd like to parse
return Phan\Language\Context
Example #1
0
File: Phan.php Project: tpunt/phan
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return bool
  * We emit messages to the configured printer and return
  * true if issues were found.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list) : bool
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (($real = realpath($file_path)) === false || !file_exists($real)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit(EXIT_SUCCESS);
     }
     // With parsing complete, we need to tell the code base to
     // start hydrating any requested elements on their way out.
     // Hydration expands class types, imports parent methods,
     // properties, etc., and does stuff like that.
     //
     // This is an optimization that saves us a significant
     // amount of time on very large code bases. Instead of
     // hydrating all classes, we only hydrate the things we
     // actually need. When running as multiple processes this
     // lets us only need to do hydrate a subset of classes.
     $code_base->setShouldHydrateRequestedElements(true);
     // We used to scan all classes here, but now we do it
     // on demand after hydration.
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // Filter out any files that are to be excluded from
     // analysis
     $analyze_file_path_list = array_filter($analyze_file_path_list, function ($file_path) {
         return !self::isExcludedAnalysisFile($file_path);
     });
     // Get the count of all files we're going to analyze
     $file_count = count($analyze_file_path_list);
     // Prevent an ugly failure if we have no files to
     // analyze.
     if (0 == $file_count) {
         return false;
     }
     // Get a map from process_id to the set of files that
     // the given process should analyze in a stable order
     $process_file_list_map = (new Ordering($code_base))->orderForProcessCount(Config::get()->processes, $analyze_file_path_list);
     // This worker takes a file and analyzes it
     $analysis_worker = function ($i, $file_path) use($file_count, $code_base) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         Analysis::analyzeFile($code_base, $file_path);
     };
     // Determine how many processes we're running on. This may be
     // less than the provided number if the files are bunched up
     // excessively.
     $process_count = count($process_file_list_map);
     assert($process_count > 0 && $process_count <= Config::get()->processes, "The process count must be between 1 and the given number of processes. After mapping files to cores, {$process_count} process were set to be used.");
     // Check to see if we're running as multiple processes
     // or not
     if ($process_count > 1) {
         // Run analysis one file at a time, splitting the set of
         // files up among a given number of child processes.
         $pool = new ForkPool($process_file_list_map, function () {
             // Remove any issues that were collected prior to forking
             // to prevent duplicate issues in the output.
             self::getIssueCollector()->reset();
         }, $analysis_worker, function () {
             // Return the collected issues to be serialized.
             return self::getIssueCollector()->getCollectedIssues();
         });
         // Wait for all tasks to complete and collect the results.
         self::collectSerializedResults($pool->wait());
     } else {
         // Get the task data from the 0th processor
         $analyze_file_path_list = array_values($process_file_list_map)[0];
         // If we're not running as multiple processes, just iterate
         // over the file list and analyze them
         foreach ($analyze_file_path_list as $i => $file_path) {
             $analysis_worker($i, $file_path);
         }
         // Scan through all globally accessible elements
         // in the code base and emit errors for dead
         // code.
         Analysis::analyzeDeadCode($code_base);
     }
     // Get a count of the number of issues that were found
     $is_issue_found = 0 !== count(self::$issueCollector->getCollectedIssues());
     // Collect all issues, blocking
     self::display();
     return $is_issue_found;
 }
Example #2
0
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return null
  * We emit messages to STDOUT. Nothing is returned.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list)
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (!file_exists($file_path)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Update the timestamp on when it was last
             // parsed
             $code_base->setParseUpToDateForFile($file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit;
     }
     // Take a pass over all classes verifying various
     // states now that we have the whole state in
     // memory
     Analysis::analyzeClasses($code_base);
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // We can only save classes, methods, properties and
     // constants after we've merged parent classes in.
     $code_base->store();
     // Once we know what the universe looks like we
     // can scan for more complicated issues.
     $file_count = count($analyze_file_path_list);
     foreach ($analyze_file_path_list as $i => $file_path) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         // We skip anything defined as 3rd party code
         // to save a lil' time
         if (self::isExcludedAnalysisFile($file_path)) {
             continue;
         }
         // Analyze the file
         Analysis::analyzeFile($code_base, $file_path);
     }
     // Scan through all globally accessible elements
     // in the code base and emit errors for dead
     // code.
     Analysis::analyzeDeadCode($code_base);
     // Emit all log messages
     self::display();
 }
Example #3
0
 /**
  * Analyze the given set of files and emit any issues
  * found to STDOUT.
  *
  * @param CodeBase $code_base
  * A code base needs to be passed in because we require
  * it to be initialized before any classes or files are
  * loaded.
  *
  * @param string[] $file_path_list
  * A list of files to scan
  *
  * @return null
  * We emit messages to STDOUT. Nothing is returned.
  *
  * @see \Phan\CodeBase
  */
 public static function analyzeFileList(CodeBase $code_base, array $file_path_list)
 {
     $file_count = count($file_path_list);
     // We'll construct a set of files that we'll
     // want to run an analysis on
     $analyze_file_path_list = [];
     // This first pass parses code and populates the
     // global state we'll need for doing a second
     // analysis after.
     foreach ($file_path_list as $i => $file_path) {
         CLI::progress('parse', ($i + 1) / $file_count);
         // Kick out anything we read from the former version
         // of this file
         $code_base->flushDependenciesForFile($file_path);
         // If the file is gone, no need to continue
         if (!file_exists($file_path)) {
             continue;
         }
         try {
             // Parse the file
             Analysis::parseFile($code_base, $file_path);
             // Save this to the set of files to analyze
             $analyze_file_path_list[] = $file_path;
         } catch (\Throwable $throwable) {
             error_log($file_path . ' ' . $throwable->getMessage() . "\n");
         }
     }
     // Don't continue on to analysis if the user has
     // chosen to just dump the AST
     if (Config::get()->dump_ast) {
         exit(EXIT_SUCCESS);
     }
     // With parsing complete, we need to tell the code base to
     // start hydrating any requested elements on their way out.
     // Hydration expands class types, imports parent methods,
     // properties, etc., and does stuff like that.
     //
     // This is an optimization that saves us a significant
     // amount of time on very large code bases. Instead of
     // hydrating all classes, we only hydrate the things we
     // actually need. When running as multiple processes this
     // lets us only need to do hydrate a subset of classes.
     $code_base->setShouldHydrateRequestedElements(true);
     // Take a pass over all classes verifying various
     // states now that we have the whole state in
     // memory
     Analysis::analyzeClasses($code_base);
     // Take a pass over all functions verifying
     // various states now that we have the whole
     // state in memory
     Analysis::analyzeFunctions($code_base);
     // We can only save classes, methods, properties and
     // constants after we've merged parent classes in.
     // TODO: Reinstate this
     // $code_base->store();
     // Filter out any files that are to be excluded from
     // analysis
     $analyze_file_path_list = array_filter($analyze_file_path_list, function ($file_path) {
         return !self::isExcludedAnalysisFile($file_path);
     });
     // Get the count of all files we're going to analyze
     $file_count = count($analyze_file_path_list);
     // This worker takes a file and analyzes it
     $analysis_worker = function ($i, $file_path) use($file_count, $code_base) {
         CLI::progress('analyze', ($i + 1) / $file_count);
         Analysis::analyzeFile($code_base, $file_path);
     };
     // Check to see if we're running as multiple processes
     // or not
     if (Config::get()->processes > 1) {
         // Collect all issues, blocking
         self::display();
         // Run analysis one file at a time, splitting the set of
         // files up among a given number of child processes.
         $pool = new ForkPool(Config::get()->processes, $analyze_file_path_list, function () {
         }, $analysis_worker, function () {
             self::display();
         });
         // Wait for all tasks to complete
         $pool->wait();
     } else {
         // If we're not running as multiple processes, just iterate
         // over the file list and analyze them
         foreach ($analyze_file_path_list as $i => $file_path) {
             $analysis_worker($i, $file_path);
         }
         // Scan through all globally accessible elements
         // in the code base and emit errors for dead
         // code.
         Analysis::analyzeDeadCode($code_base);
         // Collect all issues, blocking
         self::display();
     }
 }