phan 설치
This commit is contained in:
Vendored
+1
@@ -0,0 +1 @@
|
||||
This directory contains tools that may be useful to users of Phan.
|
||||
+25
@@ -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" "$@"
|
||||
@@ -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 "$@"
|
||||
Vendored
+255
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+219
@@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
Vendored
+121
@@ -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();
|
||||
});
|
||||
});
|
||||
Vendored
+103
@@ -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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user