phan 설치

This commit is contained in:
2020-05-01 15:26:54 +09:00
parent a529eba693
commit 51c96b4d04
991 changed files with 239131 additions and 14 deletions
+1
View File
@@ -0,0 +1 @@
This directory contains tools that may be useful to users of Phan.
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
function usage() {
cat 1>&2 <<EOT
Usage: $0 [options for phan]
Dumps the markdown Phan would generate for each addressable element of the code.
This is similar to what is sent to language server clients.
This currently supports classes, methods, properties, and global functions.
Notes:
- Phan does not attempt to escape any embedded HTML/markdown within doc comments.
The output may contain any HTML tags, etc - clients should do any necessary escaping.
- This tool and plugin may be renamed or removed, and the output format will likely change.
- This deliberately doesn't include full info about union types, but it would be easy to add.
EOT
}
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [[ "$1" == "-h" || "$1" == "help" || "$1" == "--help" ]]; then
usage
exit 0
fi
"$DIR/../phan" --plugin "$DIR/../src/Phan/Plugin/Internal/DumpPHPDocPlugin.php" "$@"
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
#
# Usage: /path/to/phan/tool/make_ctags_for_phan_project [options for phpctags]
#
# This combines Phan and phpctags to generate a ctags file for your Phan project.
# (This includes only the files you parse and analyze)
#
# phpctags can be obtained from https://github.com/vim-php/phpctags
#
# This may be useful if you already have whitelists and/or blacklists for excluding:
#
# - classes that aren't direct dependencies of your codebase
# - `examples/` folders and test folders in `vendor/` and other third party code
#
# The resulting tags file may be combined with tags for JS, CSS, etc.
function usage() {
echo "Usage: $0 [options for phpctags]" 1>&2
}
if ! type phpctags ; then
echo "$0: Could not find phpctags in $PATH" 1>&2
echo
usage
exit 1
fi
if [ ! -f .phan/config.php ]; then
echo "Must run this from the root of a project configured to use Phan (could not find $PWD/.phan/config.php)" 1>&2
usage
exit 1
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo "Generating file list with Phan and running phpctags"
# Run phpctags on the files that Phan would parse or analyze.
# This can be a long list. Choose a default larger than 128KB (2MB) -- (run getconf ARG_MAX for the actual limit)
# Unfortunately, phpctags doesn't have an option to read file arguments from a list or stdin
# Note: Use a default memory limit higher than 128M - This is needed to parse FunctionSignatureMap.php
"$DIR/../phan" --dump-parsed-file-list | xargs --exit -s 2000000 phpctags --memory=2G --verbose "$@"
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
use Phan\AST\TolerantASTConverter\Shim;
use Phan\CodeBase;
use Phan\Language\Element\Clazz;
use Phan\Language\Element\Func;
use Phan\Language\Element\GlobalConstant;
use Phan\Language\FQSEN\FullyQualifiedClassName;
use Phan\Language\FQSEN\FullyQualifiedFunctionName;
use Phan\Language\FQSEN\FullyQualifiedGlobalConstantName;
require_once dirname(__DIR__) . '/src/requirements.php';
require_once dirname(__DIR__) . '/src/Phan/Bootstrap.php';
// If php-ast isn't loaded already, then load this file to generate equivalent
// class, constant, and function definitions.
Shim::load();
/**
* Generates PHP stubs for extensions that can be used in the autoload_internal_extension_signatures `.phan/config.php` setting.
* These are regular PHP files stubbing PHP modules, containing empty method implementations, etc.
*
* Configured stubs can be used in IDEs, analysis, etc. where extensions aren't installed or enabled (e.g. Xdebug)
* @phan-file-suppress PhanPluginDescriptionlessCommentOnPublicMethod, PhanPluginNoCommentOnPublicMethod
*/
class StubsGenerator
{
/**
* @return void (does not return)
*/
public static function printHelpAndExit(string $message = '', int $exit_code = 1) : void
{
if ($message !== '') {
echo "$message\n";
}
global $argv;
$prog_name = $argv[0];
echo <<<EOT
Usage: $prog_name [--opts]
-h, --help
Print this help message
-e, --extension
Print stubs for only the given PECL or built in extension (e.g. 'ast', 'pcntl')
EOT;
exit($exit_code);
}
/**
* The main function of the `make_stubs` script.
* See `make_stubs --help` for usage.
*/
public static function main() : void
{
$options = getopt('he:', ['help', 'extension:']);
if (isset($options['h']) || isset($options['help'])) {
self::printHelpAndExit('', 0);
}
$code_base = require_once(dirname(__DIR__) . '/src/codebase.php');
$extension_name = $options['e'] ?? $options['extension'] ?? null;
if (is_string($extension_name)) {
self::printStubsForExtension($extension_name, $code_base);
return;
}
self::printAllStubs($code_base);
}
private static function printStubsForExtension(string $extension_name, CodeBase $code_base) : void
{
$stub_collection = new StubCollection($code_base);
$reflection_extension = new ReflectionExtension($extension_name);
$extension_version = $reflection_extension->getVersion();
self::recordClassStubsForExtension($stub_collection, $reflection_extension, $code_base);
self::recordGlobalFunctionStubsForExtension($stub_collection, $reflection_extension, $code_base);
self::recordGlobalConstantStubsForExtension($stub_collection, $reflection_extension, $code_base);
echo "<" . "?php\n";
echo "// These stubs were generated by the phan stub generator.\n";
echo "// @phan-stub-for-extension $extension_name@$extension_version\n";
echo "\n";
echo $stub_collection->toString();
}
private static function recordClassStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getClassNames() as $class_name) {
try {
$class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString($class_name);
} catch (Exception $e) {
// only possible if module info is wrong
fwrite(STDERR, "Failed to parse fqsen of class $class_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
fwrite(STDERR, "Failed to find class $class_fqsen\n");
continue;
}
$stub_collection->addClazz($code_base->getClassByFQSEN($class_fqsen));
}
}
private static function recordGlobalFunctionStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getFunctions() as $function_name => $unused_reflection_function) {
try {
$function_fqsen = FullyQualifiedFunctionName::fromFullyQualifiedString($function_name);
} catch (Exception $e) {
// only possible if module info is wrong
fwrite(STDERR, "Failed to parse fqsen of function $function_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasFunctionWithFQSEN($function_fqsen)) {
fwrite(STDERR, "Failed to find function $function_fqsen\n");
continue;
}
$stub_collection->addFunc($code_base->getFunctionByFQSEN($function_fqsen));
}
}
private static function recordGlobalConstantStubsForExtension(
StubCollection $stub_collection,
ReflectionExtension $reflection_extension,
CodeBase $code_base
) : void {
foreach ($reflection_extension->getConstants() as $constant_name => $_) {
try {
$const_fqsen = FullyQualifiedGlobalConstantName::fromFullyQualifiedString($constant_name);
} catch (Exception $e) {
// only possible if module info is invalid
fwrite(STDERR, "Failed to parse fqsen of global constant $constant_name : {$e->getMessage()}\n");
continue;
}
if (!$code_base->hasGlobalConstantWithFQSEN($const_fqsen)) {
fwrite(STDERR, "Failed to find global constant $const_fqsen\n");
continue;
}
$stub_collection->addGlobalConstant($code_base->getGlobalConstantByFQSEN($const_fqsen));
}
}
public static function printAllStubs(CodeBase $code_base) : void
{
$code_base->eagerlyLoadAllSignatures();
$stub_collection = new StubCollection($code_base);
$class_map = $code_base->getInternalClassMap();
echo "<" . "?php\n";
echo "// These stubs were generated by the phan stub generator.\n";
foreach ($class_map as $class) {
$stub_collection->addClazz($class);
}
$function_map = $code_base->getFunctionMap();
foreach ($function_map as $function) {
if ($function->getFQSEN()->isAlternate()) {
continue;
}
$stub_collection->addFunc($function);
}
$const_map = $code_base->getGlobalConstantMap();
foreach ($const_map as $const) {
$stub_collection->addGlobalConstant($const);
}
echo $stub_collection->toString();
}
}
StubsGenerator::main();
/**
* A representation of the collection of stubs for elements of a PHP module(a.k.a. extension).
*/
class StubCollection
{
/** @var CodeBase represents the known state of the code base we're extracting the stubs from. */
private $code_base;
public function __construct(CodeBase $code_base)
{
$this->code_base = $code_base;
}
/** @var string[][] a list of class stubs for a PHP module */
public $class_stubs = [];
/** @var string[][] a list of function stubs for a PHP module */
public $function_stubs = [];
/** @var string[][] a list of global constant stubs for a PHP module */
public $global_constant_stubs = [];
public function addClazz(Clazz $class) : void
{
list($namespace, $name) = $class->toStubInfo($this->code_base);
$this->class_stubs[$namespace][(string)$class->getFQSEN()] = $name;
}
public function addGlobalConstant(GlobalConstant $global_constant) : void
{
list($namespace, $name) = $global_constant->toStubInfo();
$this->global_constant_stubs[$namespace][(string)$global_constant->getFQSEN()] = $name;
}
public function addFunc(Func $function) : void
{
list($namespace, $name) = $function->toStubInfo();
$this->function_stubs[$namespace][(string)$function->getFQSEN()] = $name;
}
/** @return string[][] */
public function toCombinedStubs() : array
{
$result = [];
foreach ($this->class_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
foreach ($this->function_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
foreach ($this->global_constant_stubs as $namespace => $stubs) {
ksort($stubs, SORT_NATURAL);
$result[$namespace] = array_merge($result[$namespace] ?? [], $stubs);
}
return $result;
}
/**
* Returns the accumulated stubs converted to inline PHP code.
*/
public function toString() : string
{
$parts = [];
foreach ($this->toCombinedStubs() as $namespace => $stubs) {
$concatenated_stubs_representation = implode('', $stubs);
$namespace_repr = ($namespace === '' ? '' : "$namespace ");
$parts[] = sprintf("namespace %s{\n%s}\n", $namespace_repr, $concatenated_stubs_representation);
}
return implode("\n", $parts);
}
}
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env php
<?php
// pdep is a tool to help explore dependencies of classes.
use Phan\CLIBuilder;
use Phan\Phan;
define("PDEP_IGNORE_STATIC", 1 << 0);
define("PDEP_HIDE_LABELS", 1 << 1);
/** Prints a usage message for 'pdep' and exits. */
function pdep_usage(int $status) : void {
global $argv;
echo <<<EOT
Usage: {$argv[0]} [options] [files or classes...]
-c, --find-classes Find classes that depend on the passed files or classes
-f, --find-files Find files that depend on the passed files or classes
-i, --import Import class graph from json file previously generated
-j, --json JSON output of entire class and file graphs plus metadata
unless a depth and starting node is given, then you get
a json file with just the dependency list for that node
-g, --graph Graphviz dot output
-m, --graphml GraphML output
--hide-labels Labels are hidden - hover in yEd to see them
--ignore-static Don't include static calls and static var dependencies
-d, --depth <depth_level>
When walking the dependency graph, limit it to this depth. For
example,
{$argv[0]} -f -d 1 MyClass
would show only the files that directly depend on MyClass.
-l, --file-list <filelist.txt>
-q, --quick Uses Phan's --quick mode
-p, --progress-bar Show progress bar
-h, --help This help
If no filenames or classnames are provided, it will generate the
full dependency tree.
Note that this tool will read your local .phan/config.php and pick out
the list of files to scan/not scan from there. Or, you can provide it
with a file list.
Examples:
{$argv[0]} -f src/Phan/PluginV3/PluginAwarePreAnalysisVisitor.php
{$argv[0]} -c -g '\Phan\PluginV3\PluginAwarePreAnalysisVisitor' | dot -Tpng > graph.png
{$argv[0]} -c -d 2 -g '\Phan\Language\Type\ClassStringType' | dot -Kfdp -Tpng > graph.png
EOT;
exit($status);
}
call_user_func(static function () : void {
global $argv;
$tool_dir = dirname($argv[0]);
$depth = 0;
$cmd = '';
$graph_flags = 0;
$graph_file = '';
$options = getopt(
"cfjgmhpqd:l:i:",
[
'import',
'json',
'graph',
'graphml',
'ignore-static',
'hide-labels',
'find-classes',
'find-files',
'file-list:',
'progress-bar',
'depth:',
'help',
],
$optind
);
if (isset($options['find-classes'])) {
$options['c'] = false;
}
if (isset($options['find-files'])) {
$options['f'] = false;
}
if (isset($options['depth'])) {
$options['d'] = false;
}
if (isset($options['json'])) {
$options['j'] = false;
}
if (isset($options['import'])) {
$options['i'] = false;
}
$mode = null;
if (isset($options['c'])) {
if (isset($options['f'])) {
echo "ERROR: Cannot pass both -c and -f\n";
pdep_usage(1);
}
$mode = 'class';
} elseif (isset($options['f'])) {
$mode = 'file';
}
if (isset($options['d'])) {
if (empty($mode)) {
echo "ERROR: You must specify either -c or -f\n";
pdep_usage(1);
}
}
if (isset($options['h']) || isset($options['help'])) {
pdep_usage(0);
return;
}
$code_base = require_once __DIR__ . '/../src/codebase.php';
require_once __DIR__ . '/../src/Phan/Bootstrap.php';
$cli_builder = new CLIBuilder();
foreach ($options as $opt => $value) {
switch ($opt) {
case 'j':
case 'json':
$cmd = 'json';
break;
case 'g':
case 'graph':
$cmd = "graph";
break;
case 'm':
case 'graphml':
$cmd = "graphml";
break;
case 'ignore-static':
$graph_flags |= \PDEP_IGNORE_STATIC;
break;
case 'hide-labels':
$graph_flags |= \PDEP_HIDE_LABELS;
break;
case 'p':
case 'progress-bar':
$cli_builder->setOption('progress-bar');
break;
case 'q':
case 'quick':
$cli_builder->setOption('quick');
break;
case 'd':
case 'depth':
$depth = filter_var($value, FILTER_VALIDATE_INT);
if ($depth === false) {
echo "ERROR: Invalid depth '$value' (expected int)\n";
pdep_usage(1);
}
break;
case 'l':
case 'file-list':
// @phan-suppress-next-line PhanPossiblyNullTypeArgument
$cli_builder->setOption('file-list', $value);
break;
case 'i':
case 'import':
$cli_builder->setOption('no-progress-bar');
$graph_file = (string)$value;
break;
}
}
if ($cmd === 'json' && $depth === 0) {
$mode = '';
}
// Args for PDEP
$arg_string = implode(' ', array_slice($argv, $optind));
if (empty($arg_string) && $depth) {
echo "ERROR: You must specify a starting node when specifying a depth\n";
pdep_usage(1);
}
$cwd = \getcwd();
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
$cli_builder->setOption('allow-polyfill-parser');
$cli_builder->setOption('processes', '1');
$cli_builder->setOption('plugin', dirname($tool_dir) . '/src/Phan/Plugin/Internal/DependencyGraphPlugin.php');
$cli_builder->setOption('config-file', "$cwd/.phan/pdep_config.php");
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
$cli = $cli_builder->build();
$putenv = static function (string $key, string $value) : void {
putenv("$key=$value");
$_ENV[$key] = $value;
};
$putenv("PDEP_CMD", $cmd);
$putenv("PDEP_MODE", (string)$mode);
$putenv("PDEP_DEPTH", (string)$depth);
$putenv("PDEP_ARGS", $arg_string);
$putenv("PDEP_GRAPH_FLAGS", (string)$graph_flags);
if (isset($options['i'])) {
$data = @file_get_contents($graph_file);
if (!is_string($data)) {
echo "Unable to read graph file '$graph_file'\n";
exit(1);
}
$cached_graph = @json_decode($data, true);
if (!is_array($cached_graph)) {
echo "Invalid JSON contents of graph file '$graph_file': " . (json_last_error() !== JSON_ERROR_NONE ? json_last_error_msg() : 'expected array, got ' . gettype($cached_graph)) . "\n";
exit(1);
}
require_once __DIR__ . '/../src/Phan/Plugin/Internal/DependencyGraphPlugin.php';
(new \Phan\Plugin\Internal\DependencyGraphPlugin)->processGraph($cached_graph);
} else {
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
Phan::analyzeFileList($code_base, /** @return string[] */ static function () use($cli) : array {
return $cli->getFileList();
});
}
});
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env php
<?php
/**
* phantasm is a tool to assemble information about the codebase and then aggressively optimize it.
* This modifies the files that would be analyzed by Phan.
*
* Currently, the only optimization step this performs is replacing class constants with simple values.
* (Opcache can only replace class constants within the class declaring the constant, so that helps with that)
*
* See tests/phantasm_test for examples of the output.
*
* Future plans:
*
* - Expand the supported types of expressions that can be replaced or used as replacements
* - Inline instance and method calls across files (e.g. methods returning constants or simple expressions)
* - Add option to inline getters/setters, where the real type is known and there are no subclasses (by default, assume the original won't throw a TypeError)
* - Add ways to integrate this with building a phar file (unsafe)
* - Integrate other optimizations Phan can already do, such as --automatic-fix for NotFullyQualifiedUsagePlugin.
* - Use Phan's signature map/documentation map information about which constants, classes, and functions/methods are internal
*/
use Phan\CLI;
use Phan\CLIBuilder;
use Phan\Config;
use Phan\Phan;
/**
* Print usage for phantasm and exit.
*/
function phantasm_usage(int $status) : void {
global $argv;
$program = $argv[0];
fwrite($status !== 0 ? STDERR : STDOUT, <<<EOT
Usage: $program [--in-place|--output-directory <dir>]
Options:
--in-place: Modify files in place. This may result in loss of uncommitted changes.
--output-directory <dir>: Save new files to a different directory.
-h, --help: Output this help message.
EOT
);
exit($status);
}
call_user_func(static function () : void {
global $argv;
$options = getopt(
"hop:",
[
'help',
'in-place',
'progress-bar',
'output-directory:',
],
$optind
);
$has_any_option = static function (string ...$arg_names) use ($options) : bool {
foreach ($arg_names as $arg) {
if (array_key_exists($arg, $options)) {
return true;
}
}
return false;
};
if ($has_any_option('h', 'help')) {
phantasm_usage(0);
return;
}
$remaining_argv = array_slice($argv, $optind);
if (count($remaining_argv) !== 0) {
fwrite(STDERR, "ERROR: unexpected arguments: " . json_encode($remaining_argv) . "\n");
phantasm_usage(1);
return;
}
$code_base = require_once(__DIR__ . '/../src/codebase.php');
require_once(__DIR__ . '/../src/Phan/Bootstrap.php');
$cli_builder = new CLIBuilder();
if ($has_any_option('p', 'progress-bar')) {
$cli_builder->setOption('progress-bar');
}
if ($has_any_option('p', 'progress-bar')) {
$cli_builder->setOption('progress-bar');
}
$cli_builder->setOption('force-polyfill-parser-with-original-tokens');
$cli_builder->setOption('plugin', dirname(__DIR__) . '/src/Phan/Plugin/Internal/PhantasmPlugin.php');
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
$cli = $cli_builder->build();
if ($has_any_option('o', 'output-directory')) {
$dir = $options['output-directory'] ?? $options['o'] ?? null;
if (!is_string($dir) || $dir === '') {
CLI::printErrorToStderr("Invalid --output-directory\n");
phantasm_usage(1);
return;
}
if (isset($options['in-place'])) {
CLI::printErrorToStderr("Cannot combine --output-directory and --in-place\n");
phantasm_usage(1);
}
$resolved_dir = Config::projectPath($dir);
if ($resolved_dir === __DIR__ || realpath($resolved_dir) === realpath(__DIR__)) {
fwrite(STDERR, "--output-directory cannot be the same directory as the project directory. Use --in-place to overwrite a project in place\n");
phantasm_usage(1);
}
fwrite(STDERR, "phantasm: Going to output new contents to '$resolved_dir'\n");
$_ENV['PHANTASM_OUTPUT_DIRECTORY'] = $resolved_dir;
} else if (isset($options['in-place'])) {
$_ENV['PHANTASM_MODIFY_IN_PLACE'] = '1';
} else {
CLI::printErrorToStderr("Must either pass --output-directory <dir> or --in-place\n");
phantasm_usage(1);
}
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
Phan::analyzeFileList($code_base, /** @return string[] */ static function () use($cli) : array {
return $cli->getFileList();
});
});
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env php
<?php
/** phoogle is a tool to locate functions based on their signatures. */
use Phan\CLIBuilder;
use Phan\Phan;
/**
* Print usage for phoogle and exit.
*/
function phoogle_usage(int $status) : void {
global $argv;
$program = $argv[0];
fwrite($status !== 0 ? STDERR : STDOUT, <<<EOT
Usage: $program [options] 'paramType1 -> paramType2 -> returnType'
Options:
-h, --help: Print this help message to stdout.
-p, --progress-bar: Show a progress bar.
-l, --limit <count>: Number of search results to show (defaults to 10)
Set this to -1 for all results.
Examples:
Look for user-defined or internal functions returning an array of reflection methods
$program 'ReflectionMethod[]'
Look for functions that return a string, given a string and an array
$program 'string -> array -> string'
Look for all functions that can be used to get a method (\\Phan\\Language\\Element\\Method) from a CodeBase (\\Phan\\CodeBase)
(Assumes Phan is in the parsed file list)
$program --limit -1 'CodeBase->Method'
Notes:
The order of the parameters is deliberately ignored.
More CLI options will be added in the future.
EOT
);
exit($status);
}
call_user_func(static function () : void {
global $argv;
$options = getopt(
"hpl:",
[
'help',
'progress-bar',
'limit:',
],
$optind
);
$has_any_option = static function (string ...$arg_names) use ($options) : bool {
foreach ($arg_names as $arg) {
if (array_key_exists($arg, $options)) {
return true;
}
}
return false;
};
if ($has_any_option('h', 'help')) {
phoogle_usage(0);
return;
}
$remaining_argv = array_slice($argv, $optind);
if (count($remaining_argv) !== 1) {
fwrite(STDERR, "ERROR: Expected 1 argument with the function/method signature to search for, got " . count($remaining_argv) . "\n");
phoogle_usage(1);
}
$code_base = require_once(__DIR__ . '/../src/codebase.php');
require_once(__DIR__ . '/../src/Phan/Bootstrap.php');
$cli_builder = new CLIBuilder();
if ($has_any_option('p', 'progress-bar')) {
$cli_builder->setOption('progress-bar');
}
$limit_raw = $options['limit'] ?? $options['l'] ?? '10';
if (is_string($limit_raw)) {
$limit = filter_var($limit_raw, FILTER_VALIDATE_INT);
if ($limit === -1) {
$limit = PHP_INT_MAX;
} elseif ($limit <= 0) {
echo "ERROR: limit must be a positive integer, got $limit_raw\n";
phoogle_usage(1);
}
$_ENV['PHOOGLE_LIMIT'] = $limit;
}
$cli_builder->setOption('find-signature', $remaining_argv[0]);
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
$cli = $cli_builder->build();
// @phan-suppress-next-line PhanThrowTypeAbsentForCall
Phan::analyzeFileList($code_base, /** @return string[] */ static function () use($cli) : array {
return $cli->getFileList();
});
});