1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 09:37:59 +01:00

Add way to find all references to a given class or method in the codebase

This commit is contained in:
Matthew Brown 2017-02-27 01:30:44 -05:00
parent bb7f8e320c
commit d9433c9491
4 changed files with 104 additions and 13 deletions

View File

@ -24,7 +24,7 @@ $options = getopt(
[ [
'help', 'debug', 'config:', 'monochrome', 'show-info:', 'diff', 'help', 'debug', 'config:', 'monochrome', 'show-info:', 'diff',
'file:', 'self-check', 'update-docblocks', 'output-format:', 'file:', 'self-check', 'update-docblocks', 'output-format:',
'find-dead-code', 'init', 'find-dead-code', 'init', 'find-references-to:',
] ]
); );
@ -88,6 +88,9 @@ Options:
--find-dead-code --find-dead-code
Look for dead code Look for dead code
--find-references-to=[class|method]
Searches the codebase for references to the given fully-qualified class or method,
where method is in the format class::methodName
HELP; HELP;
@ -237,6 +240,8 @@ $is_diff = isset($options['diff']);
$find_dead_code = isset($options['find-dead-code']); $find_dead_code = isset($options['find-dead-code']);
$find_references_to = isset($options['find-references-to']) ? $options['find-references-to'] : null;
$update_docblocks = isset($options['update-docblocks']); $update_docblocks = isset($options['update-docblocks']);
$project_checker = new ProjectChecker( $project_checker = new ProjectChecker(
@ -245,7 +250,8 @@ $project_checker = new ProjectChecker(
$output_format, $output_format,
$debug, $debug,
$update_docblocks, $update_docblocks,
$find_dead_code $find_dead_code || $find_references_to !== null,
$find_references_to
); );
// initialise custom config, if passed // initialise custom config, if passed

View File

@ -407,7 +407,10 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo
} }
} }
if ($context->collect_references && $context->check_variables) { if ($context->collect_references &&
!$this->getFileChecker()->project_checker->find_references_to &&
$context->check_variables
) {
foreach ($context->vars_possibly_in_scope as $var_name => $_) { foreach ($context->vars_possibly_in_scope as $var_name => $_) {
if (strpos($var_name, '->') === false && if (strpos($var_name, '->') === false &&
$var_name !== '$this' && $var_name !== '$this' &&

View File

@ -54,6 +54,11 @@ class ProjectChecker
*/ */
public $collect_references = false; public $collect_references = false;
/**
* @var string|null
*/
public $find_references_to;
/** /**
* @var bool * @var bool
*/ */
@ -176,7 +181,8 @@ class ProjectChecker
* @param boolean $debug_output * @param boolean $debug_output
* @param string $output_format * @param string $output_format
* @param bool $update_docblocks * @param bool $update_docblocks
* @param bool $find_dead_code * @param bool $collect_references
* @param string $find_references_to
*/ */
public function __construct( public function __construct(
$use_color = true, $use_color = true,
@ -184,13 +190,15 @@ class ProjectChecker
$output_format = self::TYPE_CONSOLE, $output_format = self::TYPE_CONSOLE,
$debug_output = false, $debug_output = false,
$update_docblocks = false, $update_docblocks = false,
$find_dead_code = false $collect_references = false,
$find_references_to = null
) { ) {
$this->use_color = $use_color; $this->use_color = $use_color;
$this->show_info = $show_info; $this->show_info = $show_info;
$this->debug_output = $debug_output; $this->debug_output = $debug_output;
$this->update_docblocks = $update_docblocks; $this->update_docblocks = $update_docblocks;
$this->collect_references = $find_dead_code; $this->collect_references = $collect_references;
$this->find_references_to = $find_references_to;
if (!in_array($output_format, [self::TYPE_CONSOLE, self::TYPE_JSON, self::TYPE_EMACS])) { if (!in_array($output_format, [self::TYPE_CONSOLE, self::TYPE_JSON, self::TYPE_EMACS])) {
throw new \UnexpectedValueException('Unrecognised output format ' . $output_format); throw new \UnexpectedValueException('Unrecognised output format ' . $output_format);
@ -279,7 +287,31 @@ class ProjectChecker
} }
if ($this->collect_references) { if ($this->collect_references) {
$this->checkClassReferences(); if ($this->find_references_to) {
if (strpos($this->find_references_to, '::') !== false) {
$locations = $this->findReferencesToMethod($this->find_references_to);
} else {
$locations = $this->findReferencesToClassLike($this->find_references_to);
}
foreach ($locations as $location) {
$snippet = $location->getSnippet();
$snippet_bounds = $location->getSnippetBounds();
$selection_bounds = $location->getSelectionBounds();
$selection_start = $selection_bounds[0] - $snippet_bounds[0];
$selection_length = $selection_bounds[1] - $selection_bounds[0];
echo $location->file_name . ':' . $location->getLineNumber() . PHP_EOL .
($this->use_color
? substr($snippet, 0, $selection_start) .
"\e[97;42m" . substr($snippet, $selection_start, $selection_length) .
"\e[0m" . substr($snippet, $selection_length + $selection_start)
: $snippet
) . PHP_EOL . PHP_EOL;
}
}
} }
IssueBuffer::finish(true, (int)$start_checks, $this->visited_files); IssueBuffer::finish(true, (int)$start_checks, $this->visited_files);
@ -325,6 +357,52 @@ class ProjectChecker
} }
} }
/**
* @param string $method_id
* @return \Psalm\CodeLocation[]
*/
public function findReferencesToMethod($method_id)
{
list($fq_class_name, $method_name) = explode('::', $method_id);
if (!isset(ClassLikeChecker::$storage[strtolower($fq_class_name)])) {
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
}
$class_storage = ClassLikeChecker::$storage[strtolower($fq_class_name)];
if (!isset($class_storage->methods[strtolower($method_name)])) {
die('Method ' . $method_id . ' cannot be found' . PHP_EOL);
}
$method_storage = $class_storage->methods[strtolower($method_name)];
if ($method_storage->referencing_locations === null) {
die('No references found for ' . $method_id . PHP_EOL);
}
return $method_storage->referencing_locations;
}
/**
* @param string $fq_class_name
* @return \Psalm\CodeLocation[]
*/
public function findReferencesToClassLike($fq_class_name)
{
if (!isset(ClassLikeChecker::$storage[strtolower($fq_class_name)])) {
die('Class ' . $fq_class_name . ' cannot be found' . PHP_EOL);
}
$class_storage = ClassLikeChecker::$storage[strtolower($fq_class_name)];
if ($class_storage->referencing_locations === null) {
die('No references found for ' . $fq_class_name . PHP_EOL);
}
return $class_storage->referencing_locations;
}
/** /**
* @return void * @return void
*/ */

View File

@ -697,12 +697,16 @@ class CallChecker
continue; continue;
} }
if ($var_id === '$this') {
$does_class_exist = true;
} else {
$does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName( $does_class_exist = ClassLikeChecker::checkFullyQualifiedClassLikeName(
$fq_class_name, $fq_class_name,
$statements_checker->getFileChecker(), $statements_checker->getFileChecker(),
new CodeLocation($statements_checker->getSource(), $stmt->var), new CodeLocation($statements_checker->getSource(), $stmt->var),
$statements_checker->getSuppressedIssues() $statements_checker->getSuppressedIssues()
); );
}
if (!$does_class_exist) { if (!$does_class_exist) {
return $does_class_exist; return $does_class_exist;