dep: update phan

This commit is contained in:
2021-10-12 01:36:02 +09:00
parent c3c99c5f76
commit 43ec361d03
131 changed files with 4268 additions and 2452 deletions
Vendored Regular → Executable
View File
Vendored Regular → Executable
View File
+5
View File
@@ -393,12 +393,15 @@ return [
'tool/phan_repl_helpers.php',
'internal/dump_fallback_ast.php',
'internal/dump_html_styles.php',
'internal/emit_signature_map_for_php_version.php',
'internal/extract_arg_info.php',
'internal/flatten_signature_map.php',
'internal/internalsignatures.php',
'internal/line_deleter.php',
'internal/package.php',
'internal/reflection_completeness_check.php',
'internal/sanitycheck.php',
'internal/sort_signature_map.php',
'vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.php',
'vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.php',
'vendor/phpdocumentor/reflection-docblock/src/DocBlock.php',
@@ -651,6 +654,8 @@ return [
'RemoveDebugStatementPlugin',
'UnsafeCodePlugin',
'DeprecateAliasPlugin',
// Suggest '@return never'
'.phan/plugins/AddNeverReturnTypePlugin.php',
// Still have false positives to suppress
// '.phan/plugins/StaticVariableMisusePlugin.php',
@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
use ast\Node;
use Phan\Analysis\BlockExitStatusChecker;
use Phan\CodeBase;
use Phan\Language\Element\Func;
use Phan\Language\Element\Method;
use Phan\Language\Type\NeverType;
use Phan\PluginV3;
use Phan\PluginV3\AnalyzeFunctionCapability;
use Phan\PluginV3\AnalyzeMethodCapability;
/**
* This plugin checks if a function or method will not return (and has no overrides).
* If the function doesn't have a return type of never.
* then this plugin will emit an issue.
*
* It hooks into two events:
*
* - analyzeMethod
* Once all methods are parsed, this method will be called
* on every method in the code base
*
* - analyzeFunction
* Once all functions have been parsed, this method will
* be called on every function in the code base.
*
* A plugin file must
*
* - Contain a class that inherits from \Phan\PluginV3
*
* - End by returning an instance of that class.
*
* It is assumed without being checked that plugins aren't
* mangling state within the passed code base or context.
*
* Note: When adding new plugins,
* add them to the corresponding section of README.md
*/
final class NeverReturnPlugin extends PluginV3 implements
AnalyzeFunctionCapability,
AnalyzeMethodCapability
{
/**
* @param CodeBase $code_base
* The code base in which the method exists
*
* @param Method $method
* A method being analyzed
*
* @override
*/
public function analyzeMethod(
CodeBase $code_base,
Method $method
): void {
$stmts_list = self::getStatementListToAnalyze($method);
if ($stmts_list === null) {
// check for abstract methods, generators, etc.
return;
}
if ($method->getFQSEN() !== $method->getDefiningFQSEN()) {
// Check if this was inherited by a descendant class.
return;
}
if ($method->getUnionType()->hasType(NeverType::instance(false))) {
return;
}
if ($method->isOverriddenByAnother()) {
return;
}
// This modifies the nodes in place, check this last
if (!BlockExitStatusChecker::willUnconditionallyNeverReturn($stmts_list)) {
return;
}
self::emitIssue(
$code_base,
$method->getContext(),
'PhanPluginNeverReturnMethod',
"Method {METHOD} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead",
[$method->getRepresentationForIssue(), $method->getUnionType(), 'never']
);
}
/**
* @param CodeBase $code_base
* The code base in which the function exists
*
* @param Func $function
* A function or closure being analyzed
*
* @override
*/
public function analyzeFunction(
CodeBase $code_base,
Func $function
): void {
$stmts_list = self::getStatementListToAnalyze($function);
if ($stmts_list === null) {
// check for abstract methods, generators, etc.
return;
}
if ($function->getUnionType()->hasType(NeverType::instance(false))) {
return;
}
// This modifies the nodes in place, check this last
if (!BlockExitStatusChecker::willUnconditionallyNeverReturn($stmts_list)) {
return;
}
self::emitIssue(
$code_base,
$function->getContext(),
'PhanPluginNeverReturnFunction',
"Function {FUNCTION} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead",
[$function->getRepresentationForIssue(), $function->getUnionType(), 'never']
);
}
/**
* @param Func|Method $func
* @return ?Node - returns null if there's no statement list to analyze
*/
private static function getStatementListToAnalyze($func): ?Node
{
if (!$func->hasNode()) {
return null;
} elseif ($func->hasYield()) {
// generators always return Generator.
return null;
}
$node = $func->getNode();
if (!$node) {
return null;
}
return $node->children['stmts'];
}
}
// Every plugin needs to return an instance of itself at the
// end of the file in which it's defined.
return new NeverReturnPlugin();
+1 -1
View File
@@ -116,7 +116,7 @@ class AvoidableGetterVisitor extends PluginAwarePostAnalysisVisitor
$this->emitPluginIssue(
$this->code_base,
(clone($this->context))->withLineNumberStart($node->lineno),
(clone $this->context)->withLineNumberStart($node->lineno),
$issue_name,
"Can replace {METHOD} with {PROPERTY}",
[ASTReverter::toShortString($node), '$this->' . $property_name]
+5 -5
View File
@@ -95,7 +95,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
$normalized_case_cond = is_object($case_cond) ? ASTReverter::toShortString($case_cond) : self::normalizeSwitchKey($case_cond);
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($case_node->lineno),
(clone $this->context)->withLineNumberStart($case_node->lineno),
'PhanPluginDuplicateSwitchCase',
"Duplicate/Equivalent switch case({STRING_LITERAL}) detected in switch statement - the later entry will be ignored in favor of case {CODE} at line {LINE}.",
[$normalized_case_cond, ASTReverter::toShortString($case_constant_set[$cond_key]->children['cond']), $case_constant_set[$cond_key]->lineno],
@@ -171,7 +171,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
if ($old_index !== null) {
$this->emitPluginIssue(
$this->code_base,
(clone($this->context))->withLineNumberStart($children[$i]->lineno),
(clone $this->context)->withLineNumberStart($children[$i]->lineno),
'PhanPluginDuplicateSwitchCaseLooseEquality',
"Switch case({STRING_LITERAL}) is loosely equivalent (==) to an earlier case ({STRING_LITERAL}) in switch statement - the earlier entry may be chosen instead.",
[self::normalizeSwitchKey($values_to_check[$i]), self::normalizeSwitchKey($values_to_check[$old_index])],
@@ -227,7 +227,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
$normalized_arm_expr_cond = ASTReverter::toShortString($arm_expr_cond);
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($lineno),
(clone $this->context)->withLineNumberStart($lineno),
'PhanPluginDuplicateMatchArmExpression',
"Duplicate match arm expression({STRING_LITERAL}) detected in match expression - the later entry will be ignored in favor of expression {CODE} at line {LINE}.",
[$normalized_arm_expr_cond, ASTReverter::toShortString($arm_expr_constant_set[$cond_key][0]), $arm_expr_constant_set[$cond_key][1]],
@@ -302,7 +302,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
if (is_string($key) && strncmp($key, self::HASH_PREFIX, strlen(self::HASH_PREFIX)) === 0) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($entry->lineno),
(clone $this->context)->withLineNumberStart($entry->lineno),
'PhanPluginDuplicateArrayKeyExpression',
"Duplicate dynamic array key expression ({CODE}) detected in array - the earlier entry at line {LINE} will be ignored if the expression had the same value.",
[ASTReverter::toShortString($entry->children['key']), $old_entry->lineno],
@@ -315,7 +315,7 @@ class DuplicateArrayKeyVisitor extends PluginAwarePostAnalysisVisitor
$normalized_key = self::normalizeKey($key);
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($entry->lineno),
(clone $this->context)->withLineNumberStart($entry->lineno),
'PhanPluginDuplicateArrayKey',
"Duplicate/Equivalent array key value({STRING_LITERAL}) detected in array - the earlier entry {CODE} at line {LINE} will be ignored.",
[$normalized_key, ASTReverter::toShortString($old_entry->children['key']), $old_entry->lineno],
@@ -351,7 +351,7 @@ class RedundantNodePostAnalysisVisitor extends PluginAwarePostAnalysisVisitor
if ($hash === $prev_hash) {
$this->emitPluginIssue(
$this->code_base,
(clone($this->context))->withLineNumberStart($child->lineno ?? $node->lineno),
(clone $this->context)->withLineNumberStart($child->lineno ?? $node->lineno),
'PhanPluginDuplicateAdjacentStatement',
"Statement {CODE} is a duplicate of the statement on the above line. Suppress this issue instance if there's a good reason for this.",
[ASTReverter::toShortString($child)]
@@ -476,7 +476,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
if (isset($condition_set[$cond_hash])) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($cond->lineno ?? $children[$i]->lineno),
(clone $this->context)->withLineNumberStart($cond->lineno ?? $children[$i]->lineno),
'PhanPluginDuplicateIfCondition',
'Saw the same condition {CODE} in an earlier if/elseif statement',
[ASTReverter::toShortString($cond)]
@@ -490,7 +490,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
if (($stmts->children ?? null) && ASTHasher::hash($stmts) === ASTHasher::hash($children[$N - 2]->children['stmts'])) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($children[$N - 1]->lineno),
(clone $this->context)->withLineNumberStart($children[$N - 1]->lineno),
'PhanPluginDuplicateIfStatements',
'The statements of the else duplicate the statements of the previous if/elseif statement with condition {CODE}',
[ASTReverter::toShortString($children[$N - 2]->children['cond'])]
@@ -522,7 +522,7 @@ class RedundantNodePreAnalysisVisitor extends PluginAwarePreAnalysisVisitor
if ($prev_hash === $cur_hash) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($catches[$i]->lineno),
(clone $this->context)->withLineNumberStart($catches[$i]->lineno),
'PhanPluginDuplicateCatchStatementBody',
'The implementation of catch({CODE}) and catch({CODE}) are identical, and can be combined if the application only needs to supports php 7.1 and newer',
[
@@ -111,7 +111,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
$this->emitPluginIssue(
$this->code_base,
(clone($this->context))->withLineNumberStart($last_if_elem->children['stmts']->lineno ?? $last_if_elem->lineno),
(clone $this->context)->withLineNumberStart($last_if_elem->children['stmts']->lineno ?? $last_if_elem->lineno),
'PhanPluginEmptyStatementIf',
'Empty statement list statement detected for the last if/elseif statement',
[]
@@ -188,7 +188,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
(clone $this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
'PhanPluginEmptyStatementForLoop',
'Empty statement list statement detected for the for loop',
[]
@@ -216,7 +216,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
(clone $this->context)->withLineNumberStart($stmts_node->lineno ?? $node->lineno),
'PhanPluginEmptyStatementWhileLoop',
'Empty statement list statement detected for the while loop',
[]
@@ -244,7 +244,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($stmts_node->lineno),
(clone $this->context)->withLineNumberStart($stmts_node->lineno),
'PhanPluginEmptyStatementDoWhileLoop',
'Empty statement list statement detected for the do-while loop',
[]
@@ -273,7 +273,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($stmts_node->lineno),
(clone $this->context)->withLineNumberStart($stmts_node->lineno),
'PhanPluginEmptyStatementForeachLoop',
'Empty statement list statement detected for the foreach loop',
[]
@@ -292,7 +292,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
if (!$this->hasTODOComment($try_node->lineno, $node, $node->children['catches']->children[0]->lineno ?? $finally_node->lineno ?? null)) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($try_node->lineno),
(clone $this->context)->withLineNumberStart($try_node->lineno),
'PhanPluginEmptyStatementTryBody',
'Empty statement list statement detected for the try statement\'s body',
[]
@@ -303,7 +303,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
if (!$this->hasTODOComment($finally_node->lineno, $node)) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($finally_node->lineno),
(clone $this->context)->withLineNumberStart($finally_node->lineno),
'PhanPluginEmptyStatementTryFinally',
'Empty statement list statement detected for the try\'s finally body',
[]
@@ -348,7 +348,7 @@ final class EmptyStatementListVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($node->lineno),
(clone $this->context)->withLineNumberStart($node->lineno),
'PhanPluginEmptyStatementSwitch',
'No side effects seen for any cases of this switch statement',
[]
+1 -1
View File
@@ -126,7 +126,7 @@ class InlineHTMLPlugin extends PluginV3 implements
}
$this->emitIssue(
$code_base,
clone($context)->withLineNumberStart($token[2]),
(clone $context)->withLineNumberStart($token[2]),
$issue,
$message,
[StringUtil::jsonEncode(self::truncate($token[1]))]
@@ -144,7 +144,7 @@ class InvokePHPNativeSyntaxCheckPlugin extends PluginV3 implements
self::emitIssue(
$code_base,
clone($context)->withLineNumberStart($lineno),
(clone $context)->withLineNumberStart($lineno),
'PhanNativePHPSyntaxCheckPlugin',
'Saw error or notice for {FILE} --syntax-check: {DETAILS}',
[
@@ -182,6 +182,9 @@ class InvokeExecutionPromise
/** @var string the raw bytes from stdout with serialized data */
private $raw_stdout = '';
/** @var string */
private $fallback_error = '';
/** @var Context has the file name being analyzed */
private $context;
@@ -190,7 +193,7 @@ class InvokeExecutionPromise
public function __construct(string $binary, string $file_contents, Context $context)
{
$this->context = clone($context);
$this->context = clone $context;
$new_file_contents = Parser::removeShebang($file_contents);
// TODO: Use symfony process
// Note: We might have invalid utf-8, ensure that the streams are opened in binary mode.
@@ -252,7 +255,10 @@ class InvokeExecutionPromise
}
$this->process = $process;
self::streamPutContents($pipes[0], $new_file_contents);
error_clear_last();
if (!self::streamPutContents($pipes[0], $new_file_contents)) {
$this->fallback_error = \error_get_last()['message'] ?? '';
}
}
$this->pipes = $pipes;
@@ -290,17 +296,18 @@ class InvokeExecutionPromise
/**
* @param resource $stream stream to write $file_contents to before fclose()
* @param string $file_contents
* @return void
* See https://bugs.php.net/bug.php?id=39598
*/
private static function streamPutContents($stream, string $file_contents): void
private static function streamPutContents($stream, string $file_contents): bool
{
try {
while (strlen($file_contents) > 0) {
$bytes_written = fwrite($stream, $file_contents);
$bytes_written = with_disabled_phan_error_handler(/** @return int|false */ static function () use ($stream, $file_contents) {
return @fwrite($stream, $file_contents);
});
if ($bytes_written === false) {
error_log('failed to write in ' . __METHOD__);
return;
CLI::printWarningToStderr('failed to write in ' . __METHOD__);
return false;
}
if ($bytes_written === 0) {
$read_streams = [];
@@ -316,8 +323,8 @@ class InvokeExecutionPromise
// $stream is ready to be written to?
$bytes_written = fwrite($stream, $file_contents);
if (!$bytes_written) {
error_log('failed to write in ' . __METHOD__ . ' but the stream should be ready');
return;
CLI::printToStderr('failed to write in ' . __METHOD__ . ' but the stream should be ready');
return false;
}
}
if ($bytes_written > 0) {
@@ -327,6 +334,7 @@ class InvokeExecutionPromise
} finally {
fclose($stream);
}
return true;
}
/**
@@ -390,6 +398,14 @@ class InvokeExecutionPromise
if (!$this->done) {
throw new RangeException("Called " . __METHOD__ . " too early");
}
if ($this->error === '') {
// There was an error running the process, but no output to stdout.
$result = "No output was detected. Is " . var_representation($this->binary) . " a relative or absolute path to an executable PHP binary?";
if ($this->fallback_error !== '') {
$result .= ' Error sending file contents to syntax check: ' . $this->fallback_error;
}
return $result;
}
return $this->error;
}
@@ -409,6 +425,9 @@ class InvokeExecutionPromise
return $this->binary;
}
/**
* @return never
*/
public function __wakeup()
{
$this->tmp_path = null;
@@ -111,6 +111,9 @@ class MoreSpecificElementTypePlugin extends PluginV3 implements
if ($declared_return_type->isStrictSubtypeOf($code_base, $actual_type)) {
return false;
}
if (!$actual_type->isStrictSubtypeOf($code_base, $declared_return_type)) {
return false;
}
if (!$actual_type->canCastToUnionType($declared_return_type, $code_base)) {
// Don't warn here about type mismatches such as int->string or object->array, but do warn about SubClass->BaseClass.
// Phan should warn elsewhere about those mismatches
@@ -134,7 +134,7 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($expression->lineno),
(clone $this->context)->withLineNumberStart($expression->lineno),
$issue_type,
$issue_msg,
[$function_name, $this->context->getNamespace()]
@@ -167,7 +167,9 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
}
$constant_name_lower = strtolower($constant_name);
if ($constant_name_lower === 'true' || $constant_name_lower === 'false' || $constant_name_lower === 'null') {
// These are keywords and are the same in any namespace
// These are treated similarly to keywords and are either
// 1. the same in any namespace
// 2. `use somethingelse\true [as false];`
return;
}
@@ -183,7 +185,7 @@ class NotFullyQualifiedUsageVisitor extends PluginAwarePostAnalysisVisitor
{
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($expression->lineno),
(clone $this->context)->withLineNumberStart($expression->lineno),
self::NotFullyQualifiedGlobalConstant,
'Expected usage of {CONST} to be fully qualified or have a use statement but none were found in namespace {NAMESPACE}',
[$constant_name, $this->context->getNamespace()]
@@ -4,6 +4,8 @@ declare(strict_types=1);
use Microsoft\PhpParser\Node\Expression\CallExpression;
use Microsoft\PhpParser\Node\QualifiedName;
use Microsoft\PhpParser\Node\ReservedWord;
use Microsoft\PhpParser\Token;
use Phan\AST\TolerantASTConverter\NodeUtils;
use Phan\CodeBase;
use Phan\IssueInstance;
@@ -29,15 +31,26 @@ call_user_func(static function (): void {
$expected_name = $instance->getTemplateParameters()[0];
$edits = [];
foreach ($contents->getNodesAtLine($line) as $node) {
if (!$node instanceof QualifiedName) {
if ($node instanceof QualifiedName) {
if ($node->globalSpecifier || $node->relativeSpecifier) {
IssueFixer::debug("skip already globally or relatively specified\n");
// This is already qualified
continue;
}
$actual_name = (new NodeUtils($contents->getContents()))->phpParserNameToString($node);
} elseif ($node instanceof ReservedWord) {
// A reserved word in other contexts such as 'float'
$token = $node->children;
if (!$token instanceof Token) {
continue;
}
$actual_name = (new NodeUtils($contents->getContents()))->tokenToString($token);
} else {
IssueFixer::debug("skip wrong node kind " . get_class($node) . "\n");
continue;
}
if ($node->globalSpecifier || $node->relativeSpecifier) {
// This is already qualified
continue;
}
$actual_name = (new NodeUtils($contents->getContents()))->phpParserNameToString($node);
if ($actual_name !== $expected_name) {
IssueFixer::debug("skip '$actual_name' !== '$expected_name'\n");
continue;
}
$is_actual_call = $node->parent instanceof CallExpression;
@@ -58,7 +71,8 @@ call_user_func(static function (): void {
} else {
// Don't do this if the global function this refers to doesn't exist.
// TODO: Support namespaced functions
if (!$code_base->hasGlobalConstantWithFQSEN(FullyQualifiedGlobalConstantName::fromFullyQualifiedString($actual_name))) {
if (!$code_base->hasGlobalConstantWithFQSEN(FullyQualifiedGlobalConstantName::fromFullyQualifiedString($actual_name)) &&
!in_array(strtolower($actual_name), ['null', 'true', 'false'], true)) {
IssueFixer::debug("skip attempt to fix $actual_name because the constant was not found in the global scope\n");
return null;
}
@@ -55,7 +55,7 @@ class PHPDocInWrongCommentPlugin extends PluginV3 implements
if ($comment_string[0] === '#' && substr($comment_string, 1, 1) !== '[') {
$this->emitIssue(
$code_base,
(clone($context))->withLineNumberStart($token[2]),
(clone $context)->withLineNumberStart($token[2]),
'PhanPluginPHPDocHashComment',
'Saw comment starting with {COMMENT} in {COMMENT} - consider using {COMMENT} instead to avoid confusion with php 8.0 {COMMENT} attributes',
['#', StringUtil::jsonEncode(self::truncate(trim($comment_string))), '//', '#[']
@@ -76,7 +76,7 @@ class PHPDocInWrongCommentPlugin extends PluginV3 implements
}
$this->emitIssue(
$code_base,
(clone($context))->withLineNumberStart($token[2]),
(clone $context)->withLineNumberStart($token[2]),
'PhanPluginPHPDocInWrongComment',
'Saw possible phpdoc annotation in ordinary block comment {COMMENT}. PHPDoc comments should start with "/**" (followed by whitespace), not "/*"',
[StringUtil::jsonEncode(self::truncate($comment_string))]
+3 -3
View File
@@ -60,7 +60,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
) use (
$fmt_index,
$arg_index
): void {
): void {
if (\count($args) <= $fmt_index) {
return;
}
@@ -96,7 +96,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
) use (
$type_index,
$arg_index
): void {
): void {
if (\count($args) <= $type_index) {
return;
}
@@ -136,7 +136,7 @@ class PhanSelfCheckPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
) use (
$type_index,
$arg_index
): void {
): void {
if (\count($args) <= $type_index) {
return;
}
+2 -2
View File
@@ -706,8 +706,8 @@ class PrintfCheckerPlugin extends PluginV3 implements AnalyzeFunctionCallCapabil
* @param CodeBase $code_base
* @param Context $context
* @param string $fmt_str
* @param ConversionSpec[][] $types_of_arg contains array of ConversionSpec for
* each position in the untranslated format string.
* @param associative-array<int,array<mixed,ConversionSpec|true>> $types_of_arg contains array of ConversionSpec for
* each position in the untranslated format string.
*/
protected static function validateTranslations(CodeBase $code_base, Context $context, string $fmt_str, array $types_of_arg): void
{
+10
View File
@@ -596,6 +596,16 @@ This is only useful in applications or libraries that print output in only a few
Suppression comments can use the issue name `PhanPluginRemoveDebugAny` to suppress all issue types emitted by this plugin.
#### AddNeverReturnTypePlugin.php
This plugin checks if a function or method will not return (and has no overrides).
If the function doesn't have a return type of never.
then this plugin will emit an issue.
Closures and short error functions are currently not checked
- **PhanPluginNeverReturnMethod**: `Method {METHOD} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead`
- **PhanPluginNeverReturnFunction**: `Function {FUNCTION} never returns and has a return type of {TYPE}, but phpdoc type {TYPE} could be used instead`
### 4. Demo plugins:
These files demonstrate plugins for Phan.
@@ -121,7 +121,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
$issue_name = 'PhanPluginRedundantAssignment';
}
if ($this->context->isInLoop()) {
$this->context->deferCheckToOutermostLoop(function (Context $context_after_loop) use ($issue_name, $var_name, $variable_type): void {
$this->context->deferCheckToOutermostLoop(function (Context $context_after_loop) use ($issue_name, $var_name, $variable_type, $var): void {
$new_variable = $context_after_loop->getScope()->getVariableByNameOrNull($var_name);
if (!$new_variable) {
return;
@@ -135,7 +135,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
}
$this->emitPluginIssue(
$this->code_base,
$this->context,
(clone $this->context)->withLineNumberStart($var->lineno),
$issue_name,
'Assigning {TYPE} to variable ${VARIABLE} which already has that value',
[$variable_type, $var_name]
@@ -145,7 +145,7 @@ class RedundantAssignmentPreAnalysisVisitor extends PluginAwarePreAnalysisVisito
}
$this->emitPluginIssue(
$this->code_base,
$this->context,
(clone $this->context)->withLineNumberStart($var->lineno),
$issue_name,
'Assigning {TYPE} to variable ${VARIABLE} which already has that value',
[$expr_type, $var_name]
@@ -38,7 +38,10 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
*/
public function getAnalyzeFunctionCallClosures(CodeBase $code_base): array
{
$warn_remove_debug_call = static function (CodeBase $code_base, Context $context, FunctionInterface $function): void {
$warn_remove_debug_call = static function (CodeBase $code_base, Context $context, FunctionInterface $function, ?Node $node): void {
if ($node) {
$context = (clone $context)->withLineNumberStart($node->lineno);
}
self::emitIssue(
$code_base,
$context,
@@ -55,12 +58,12 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
Context $context,
Func $function,
array $unused_args,
?Node $unused_node = null
?Node $node = null
) use ($warn_remove_debug_call): void {
if (self::shouldSuppressDebugIssues($code_base, $context)) {
return;
}
$warn_remove_debug_call($code_base, $context, $function);
$warn_remove_debug_call($code_base, $context, $function, $node);
};
/**
* @param list<Node|string|int|float> $args the nodes for the arguments to the invocation
@@ -71,7 +74,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
Context $context,
Func $function,
array $args,
?Node $unused_node = null
?Node $node = null
) use ($warn_remove_debug_call): void {
if (self::shouldSuppressDebugIssues($code_base, $context)) {
return;
@@ -84,7 +87,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
return;
}
}
$warn_remove_debug_call($code_base, $context, $function);
$warn_remove_debug_call($code_base, $context, $function, $node);
};
/**
@@ -95,7 +98,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
Context $context,
Func $function,
array $args,
?Node $unused_node = null
?Node $node = null
) use ($warn_remove_debug_call): void {
$file = $args[0] ?? null;
if (!$file instanceof Node || $file->kind !== ast\AST_CONST || !in_array($file->children['name']->children['name'] ?? null, ['STDOUT', 'STDERR'], true)) {
@@ -106,7 +109,7 @@ class RemoveDebugStatementPlugin extends PluginV3 implements
return;
}
$warn_remove_debug_call($code_base, $context, $function);
$warn_remove_debug_call($code_base, $context, $function, $node);
};
return [
+1 -1
View File
@@ -149,7 +149,7 @@ class SleepCheckerVisitor extends PluginAwarePostAnalysisVisitor
*/
private function analyzeReturnValue($expr_node, int $lineno, array &$sleep_properties): void
{
$context = clone($this->context)->withLineNumberStart($lineno);
$context = (clone $this->context)->withLineNumberStart($lineno);
if (!($expr_node instanceof Node)) {
$this->emitPluginIssue(
$this->code_base,
+1 -1
View File
@@ -60,7 +60,7 @@ class StrictComparisonPlugin extends PluginV3 implements
$index,
$index_name,
$min_args
): void {
): void {
if (count($args) < $min_args) {
return;
}
@@ -201,7 +201,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
if ($function->isPHPInternal()) {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($node->lineno),
(clone $this->context)->withLineNumberStart($node->lineno),
self::SuspiciousParamOrderInternal,
'Suspicious order for arguments named {DETAILS} - These are being passed to parameters {DETAILS} of {FUNCTION}',
[
@@ -213,7 +213,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
} else {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($node->lineno),
(clone $this->context)->withLineNumberStart($node->lineno),
self::SuspiciousParamOrder,
'Suspicious order for arguments named {DETAILS} - These are being passed to parameters {DETAILS} of {FUNCTION} defined at {FILE}:{LINE}',
[
@@ -280,7 +280,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
if ($function->isPHPInternal()) {
$this->emitPluginIssue(
$this->code_base,
(clone($this->context))->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
(clone $this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
self::SuspiciousParamPositionInternal,
'Suspicious order for argument {DETAILS} - This is getting passed to parameter {DETAILS} of {FUNCTION}',
[
@@ -292,7 +292,7 @@ class SuspiciousParamOrderVisitor extends PluginAwarePostAnalysisVisitor
} else {
$this->emitPluginIssue(
$this->code_base,
clone($this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
(clone $this->context)->withLineNumberStart($args[$i]->lineno ?? $node->lineno),
self::SuspiciousParamPosition,
'Suspicious order for argument {DETAILS} - This is getting passed to parameter {DETAILS} of {FUNCTION} defined at {FILE}:{LINE}',
[
@@ -132,7 +132,8 @@ class UnknownElementTypePlugin extends PluginV3 implements
$inferred_types[$i] = $combined_type;
}
}
}
},
$this
);
}
@@ -350,7 +351,8 @@ class UnknownElementTypePlugin extends PluginV3 implements
$inferred_types[$i] = $combined_type;
}
}
}
},
$this
);
}
+1 -2
View File
@@ -93,7 +93,6 @@ final class UnreachableCodeVisitor extends PluginAwarePostAnalysisVisitor
continue;
}
}
$context = clone($this->context)->withLineNumberStart($next_node->lineno);
if ($this->context->isInFunctionLikeScope()) {
if ($this->context->getFunctionLikeInScope($this->code_base)->checkHasSuppressIssueAndIncrementCount('PhanPluginUnreachableCode')) {
// don't emit the below issue.
@@ -102,7 +101,7 @@ final class UnreachableCodeVisitor extends PluginAwarePostAnalysisVisitor
}
$this->emitPluginIssue(
$this->code_base,
$context,
(clone $this->context)->withLineNumberStart($next_node->lineno),
'PhanPluginUnreachableCode',
'Unreachable statement detected',
[]
+3 -3
View File
@@ -54,7 +54,7 @@ class WhitespacePlugin extends PluginV3 implements
if ($newline_position !== false) {
self::emitIssue(
$code_base,
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $newline_position)),
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $newline_position)),
self::CarriageReturn,
'The first occurrence of a carriage return ("\r") was seen here. Running "dos2unix" can fix that.'
);
@@ -63,7 +63,7 @@ class WhitespacePlugin extends PluginV3 implements
if ($tab_position !== false) {
self::emitIssue(
$code_base,
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $tab_position)),
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $tab_position)),
self::Tab,
'The first occurrence of a tab was seen here. Running "expand" can fix that.'
);
@@ -71,7 +71,7 @@ class WhitespacePlugin extends PluginV3 implements
if (preg_match('/[ \t]\r?$/mS', $file_contents, $match, PREG_OFFSET_CAPTURE)) {
self::emitIssue(
$code_base,
clone($context)->withLineNumberStart(self::calculateLine($file_contents, $match[0][1])),
(clone $context)->withLineNumberStart(self::calculateLine($file_contents, $match[0][1])),
self::WhitespaceTrailing,
'The first occurrence of trailing whitespace was seen here.'
);