Skip to content

Commit

Permalink
Add PhpdocAddMissingParamAnnotationFixer
Browse files Browse the repository at this point in the history
  • Loading branch information
keradus committed Nov 29, 2016
1 parent 37de300 commit 24c774f
Show file tree
Hide file tree
Showing 11 changed files with 680 additions and 0 deletions.
1 change: 1 addition & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ return PhpCsFixer\Config::create()
'ordered_class_elements' => true,
'ordered_imports' => true,
'php_unit_strict' => true,
'phpdoc_add_missing_param_annotation' => true,
'psr4' => true,
'strict_comparison' => true,
'strict_param' => true,
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ Choose from the list of available rules:
| "assertEquals".
| *Rule is: configurable, risky.*
* **phpdoc_add_missing_param_annotation**
| Phpdoc should contain @param for all params.
| *Rule is: configurable.*
* **phpdoc_align** [@Symfony]
| All items of the @param, @throws, @return, @var, and @type phpdoc tags
| must be aligned vertically.
Expand Down
248 changes: 248 additions & 0 deletions src/Fixer/Phpdoc/PhpdocAddMissingParamAnnotationFixer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
<?php

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Fixer\Phpdoc;

use PhpCsFixer\AbstractFunctionReferenceFixer;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\DocBlock\Line;
use PhpCsFixer\Tokenizer\Tokens;
use PhpCsFixer\WhitespacesFixerConfigAwareInterface;

/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*/
final class PhpdocAddMissingParamAnnotationFixer extends AbstractFunctionReferenceFixer implements WhitespacesFixerConfigAwareInterface
{
/**
* @var array<string, bool>
*/
private $configuration;

private static $defaultConfiguration = array(
'only_untyped' => true,
);

/**
* @param null|array<string, bool> $configuration
*/
public function configure(array $configuration = null)
{
if (null === $configuration) {
$this->configuration = self::$defaultConfiguration;

return;
}

foreach ($configuration as $key => $value) {
if (!array_key_exists($key, self::$defaultConfiguration)) {
throw new InvalidFixerConfigurationException($this->getName(), sprintf('"%s" is not handled by the fixer.', $key));
}

if (!is_bool($value)) {
throw new InvalidFixerConfigurationException($this->getName(), sprintf('Expected boolean got "%s".', is_object($value) ? get_class($value) : gettype($value)));
}

$configuration[$key] = $value;
}

$this->configuration = $configuration;
}

/**
* {@inheritdoc}
*/
public function getPriority()
{
// must be run after PhpdocNoAliasTagFixer and before PhpdocAlignFixer
return -5;
}

/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens)
{
return $tokens->isTokenKindFound(T_DOC_COMMENT);
}

/**
* {@inheritdoc}
*/
public function isRisky()
{
return false;
}

/**
* {@inheritdoc}
*/
public function fix(\SplFileInfo $file, Tokens $tokens)
{
for ($index = 0, $limit = $tokens->count(); $index < $limit; ++$index) {
$token = $tokens[$index];

if (!$token->isGivenKind(T_DOC_COMMENT)) {
continue;
}

if (1 === preg_match('/inheritdoc/i', $token->getContent())) {
continue;
}

$index = $tokens->getNextMeaningfulToken($index);

if (null === $index) {
return;
}

while ($tokens[$index]->isGivenKind(array(
T_ABSTRACT,
T_FINAL,
T_PRIVATE,
T_PROTECTED,
T_PUBLIC,
T_STATIC,
T_VAR,
))) {
$index = $tokens->getNextMeaningfulToken($index);
}

if (!$tokens[$index]->isGivenKind(T_FUNCTION)) {
continue;
}

$openIndex = $tokens->getNextTokenOfKind($index, array('('));
$index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openIndex);

$arguments = array();

foreach ($this->getArguments($tokens, $openIndex, $index) as $start => $end) {
$argumentInfo = $this->prepareArgumentInformation($tokens, $start, $end);

if (!$this->configuration['only_untyped'] || '' === $argumentInfo['type']) {
$arguments[$argumentInfo['name']] = $argumentInfo;
}
}

if (!count($arguments)) {
continue;
}

$doc = new DocBlock($token->getContent());
$lastParamLine = null;

foreach ($doc->getAnnotationsOfType('param') as $annotation) {
$pregMatched = preg_match('/^[^$]+(\$\w+).*$/s', $annotation->getContent(), $matches);

if (1 === $pregMatched) {
unset($arguments[$matches[1]]);
}

$lastParamLine = max($lastParamLine, $annotation->getEnd());
}

if (!count($arguments)) {
continue;
}

$lines = $doc->getLines();
$linesCount = count($lines);

preg_match('/^(\s*).*$/', $lines[$linesCount - 1]->getContent(), $matches);
$indent = $matches[1];

$newLines = array();

foreach ($arguments as $argument) {
$type = $argument['type'] ?: 'mixed';

if ('?' !== $type[0] && 'null' === strtolower($argument['default'])) {
$type = 'null|'.$type;
}

$newLines[] = new Line(sprintf(
'%s* @param %s %s%s',
$indent,
$type,
$argument['name'],
$this->whitespacesConfig->getLineEnding()
));
}

array_splice(
$lines,
$lastParamLine ? $lastParamLine + 1 : $linesCount - 1,
0,
$newLines
);

$token->setContent(implode('', $lines));
}
}

/**
* {@inheritdoc}
*/
public function getDescription()
{
return 'Phpdoc should contain @param for all params.';
}

/**
* @param Tokens $tokens
* @param int $start
* @param int $end
*
* @return array
*/
private function prepareArgumentInformation(Tokens $tokens, $start, $end)
{
$info = array(
'default' => '',
'name' => '',
'type' => '',
);

$sawName = false;
$sawEq = false;

for ($index = $start; $index <= $end; ++$index) {
$token = $tokens[$index];

if ($token->isComment() || $token->isWhitespace()) {
continue;
}

if ($token->isGivenKind(T_VARIABLE)) {
$sawName = true;
$info['name'] = $token->getContent();

continue;
}

if ($token->equals('=')) {
$sawEq = true;

continue;
}

if ($sawName) {
$info['default'] .= $token->getContent();
} else {
$info['type'] .= $token->getContent();
}
}

return $info;
}
}
2 changes: 2 additions & 0 deletions src/Test/AbstractIntegrationTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ protected function setUp()
* @dataProvider getTests
*
* @see doTest()
*
* @param IntegrationCase $case
*/
public function testIntegration(IntegrationCase $case)
{
Expand Down
Loading

0 comments on commit 24c774f

Please sign in to comment.