From 0e67aae21b1b2385c4c76574f18823d04826befb Mon Sep 17 00:00:00 2001 From: Matthew Brown Date: Sun, 7 Jan 2018 10:23:02 -0500 Subject: [PATCH] Allow updating of params --- src/Psalm/Checker/CommentChecker.php | 2 +- src/Psalm/Checker/FunctionLikeChecker.php | 49 +++++++++++++ .../FunctionDocblockManipulator.php | 70 ++++++++++++++++++- tests/FileManipulationTest.php | 36 ++++++++++ 4 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/Psalm/Checker/CommentChecker.php b/src/Psalm/Checker/CommentChecker.php index 020315acf..902be1cfe 100644 --- a/src/Psalm/Checker/CommentChecker.php +++ b/src/Psalm/Checker/CommentChecker.php @@ -380,7 +380,7 @@ class CommentChecker * * @return array */ - protected static function splitDocLine($return_block) + public static function splitDocLine($return_block) { $brackets = ''; diff --git a/src/Psalm/Checker/FunctionLikeChecker.php b/src/Psalm/Checker/FunctionLikeChecker.php index 7827e7140..b4e733c70 100644 --- a/src/Psalm/Checker/FunctionLikeChecker.php +++ b/src/Psalm/Checker/FunctionLikeChecker.php @@ -305,6 +305,14 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo $signature_type ) ) { + if ($project_checker->alter_code + && isset($project_checker->getIssuesToFix()['MismatchingDocblockParamType']) + ) { + $this->addOrUpdateParamType($project_checker, $function_param->name, $signature_type, true); + + return null; + } + if (IssueBuffer::accepts( new MismatchingDocblockParamType( 'Parameter $' . $function_param->name . ' has wrong type \'' . $param_type . @@ -1423,6 +1431,47 @@ abstract class FunctionLikeChecker extends SourceChecker implements StatementsSo return null; } + /** + * @param string $param_name + * @param bool $docblock_only + * + * @return void + */ + private function addOrUpdateParamType( + ProjectChecker $project_checker, + $param_name, + Type\Union $inferred_return_type, + $docblock_only = false + ) { + $manipulator = FunctionDocblockManipulator::getForFunction( + $project_checker, + $this->source->getFilePath(), + $this->getMethodId(), + $this->function + ); + $manipulator->setParamType( + $param_name, + !$docblock_only && $project_checker->php_major_version >= 7 + ? $inferred_return_type->toPhpString( + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + $project_checker->php_major_version, + $project_checker->php_minor_version + ) : null, + $inferred_return_type->toNamespacedString( + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + false + ), + $inferred_return_type->toNamespacedString( + $this->source->getAliasedClassesFlipped(), + $this->source->getFQCLN(), + true + ), + $inferred_return_type->canBeFullyExpressedInPhp() + ); + } + /** * @param bool $docblock_only * diff --git a/src/Psalm/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/FileManipulation/FunctionDocblockManipulator.php index 2bc394bea..71d60795c 100644 --- a/src/Psalm/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/FileManipulation/FunctionDocblockManipulator.php @@ -50,6 +50,27 @@ class FunctionDocblockManipulator /** @var ?string */ private $new_psalm_return_type; + /** @var array */ + private $param_typehint_area_starts = []; + + /** @var array */ + private $param_typehint_starts = []; + + /** @var array */ + private $param_typehint_ends = []; + + /** @var array */ + private $new_php_param_types = []; + + /** @var array */ + private $param_type_is_php_compatible = []; + + /** @var array */ + private $new_phpdoc_param_types = []; + + /** @var array */ + private $new_psalm_param_types = []; + /** @var string */ private $indentation; @@ -92,6 +113,8 @@ class FunctionDocblockManipulator $file_contents = $project_checker->getFileContents($file_path); + + $last_arg_position = $stmt->params ? (int) $stmt->params[count($stmt->params) - 1]->getAttribute('endFilePos') : null; @@ -158,7 +181,7 @@ class FunctionDocblockManipulator } /** - * Sets the new docblock return type + * Sets the new return type * * @param ?string $php_type * @param string $new_type @@ -177,6 +200,30 @@ class FunctionDocblockManipulator $this->return_type_is_php_compatible = $is_php_compatible; } + /** + * Sets a new param type + * + * @param string $param_name + * @param ?string $php_type + * @param string $new_type + * @param string $phpdoc_type + * @param bool $is_php_compatible + * + * @return void + */ + public function setParamType($param_name, $php_type, $new_type, $phpdoc_type, $is_php_compatible) + { + + $new_type = str_replace(['', ''], '', $new_type); + + if ($php_type) { + $this->new_php_param_types[$param_name] = $php_type; + } + $this->new_phpdoc_param_types[$param_name] = $phpdoc_type; + $this->new_psalm_param_types[$param_name] = $new_type; + $this->param_type_is_php_compatible[$param_name] = $is_php_compatible; + } + /** * Gets a new docblock given the existing docblock, if one exists, and the updated return types * and/or parameters @@ -193,6 +240,27 @@ class FunctionDocblockManipulator $parsed_docblock = ['description' => '']; } + foreach ($this->new_phpdoc_param_types as $param_name => $phpdoc_type) { + $found_in_params = false; + $new_param_block = $phpdoc_type . ' ' . '$' . $param_name; + + if (isset($parsed_docblock['specials']['param'])) { + foreach ($parsed_docblock['specials']['param'] as &$param_block) { + $doc_parts = CommentChecker::splitDocLine($param_block); + + if ($doc_parts[1] === '$' . $param_name) { + $param_block = $new_param_block; + $found_in_params = true; + break; + } + } + } + + if (!$found_in_params) { + $parsed_docblock['specials']['params'][] = $new_param_block; + } + } + if ($this->new_phpdoc_return_type) { $parsed_docblock['specials']['return'] = [$this->new_phpdoc_return_type]; } diff --git a/tests/FileManipulationTest.php b/tests/FileManipulationTest.php index 905c11951..e8fdd1bed 100644 --- a/tests/FileManipulationTest.php +++ b/tests/FileManipulationTest.php @@ -257,6 +257,42 @@ class FileManipulationTest extends TestCase '7.0', ['InvalidReturnType'], ], + 'fixMismatchingDocblockReturnType70' => [ + ' [ + ' [ '