Skip to content

Commit

Permalink
feature: NoSuperfluousPhpdocTagsFixer - support untyped and empty ann…
Browse files Browse the repository at this point in the history
…otations in phpdoc (#5792)
  • Loading branch information
supersmile2009 authored Sep 21, 2022
1 parent 39671cb commit 531eaeb
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/DocBlock/TypeExpression.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ final class TypeExpression
(?<constant> # single constant value (case insensitive), e.g.: 1, `\'a\'`
(?i)
null | true | false
| [\d.]+
| -?(?:\d+(?:\.\d*)?|\.\d+) # all sorts of numbers with or without minus, e.g.: 1, 1.1, 1., .1, -1
| \'[^\']+?\' | "[^"]+?"
| [@$]?(?:this | self | static)
(?-i)
Expand Down
44 changes: 28 additions & 16 deletions src/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use PhpCsFixer\AbstractFixer;
use PhpCsFixer\DocBlock\Annotation;
use PhpCsFixer\DocBlock\DocBlock;
use PhpCsFixer\DocBlock\TypeExpression;
use PhpCsFixer\Fixer\ConfigurableFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
Expand All @@ -33,6 +34,11 @@

final class NoSuperfluousPhpdocTagsFixer extends AbstractFixer implements ConfigurableFixerInterface
{
private const NO_TYPE_INFO = [
'types' => [],
'allows_null' => true,
];

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -320,6 +326,10 @@ private function fixFunctionDocComment(
$argumentName = $annotation->getVariableName();

if (null === $argumentName) {
if ($this->annotationIsSuperfluous($annotation, self::NO_TYPE_INFO, $currentSymbol, $shortNames)) {
$annotation->remove();
}

continue;
}

Expand Down Expand Up @@ -364,10 +374,7 @@ private function fixPropertyDocComment(
if (\count($element['types']) > 0) {
$propertyTypeInfo = $this->parseTypeHint($tokens, array_key_first($element['types']));
} else {
$propertyTypeInfo = [
'types' => [],
'allows_null' => true,
];
$propertyTypeInfo = self::NO_TYPE_INFO;
}

$docBlock = new DocBlock($content);
Expand Down Expand Up @@ -418,10 +425,7 @@ private function getArgumentsInfo(Tokens $tokens, int $start, int $end): array
if ($typeIndex !== $index) {
$info = $this->parseTypeHint($tokens, $typeIndex);
} else {
$info = [
'types' => [],
'allows_null' => true,
];
$info = self::NO_TYPE_INFO;
}

if (!$info['allows_null']) {
Expand Down Expand Up @@ -449,10 +453,7 @@ private function getReturnTypeInfo(Tokens $tokens, int $closingParenthesisIndex)

return $tokens[$colonIndex]->isGivenKind(CT::T_TYPE_COLON)
? $this->parseTypeHint($tokens, $tokens->getNextMeaningfulToken($colonIndex))
: [
'types' => [],
'allows_null' => true,
]
: self::NO_TYPE_INFO
;
}

Expand Down Expand Up @@ -519,17 +520,28 @@ private function annotationIsSuperfluous(
array $symbolShortNames
): bool {
if ('param' === $annotation->getTag()->getName()) {
$regex = '/@param\s+[^\$]+\s(?:\&\s*)?(?:\.{3}\s*)?\$\S+\s+\S/';
$regex = '{@param(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+(?:\&\s*)?(?:\.{3}\s*)?\$\S+)?(?:\s+(?<description>(?!\*+\/)\S+))?}sx';
} elseif ('var' === $annotation->getTag()->getName()) {
$regex = '/@var\s+\S+(\s+\$\S+)?(\s+)(?!\*+\/)([^$\s]+)/';
$regex = '{@var(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+\$\S+)?(?:\s+(?<description>(?!\*\/)\S+))?}sx';
} else {
$regex = '/@return\s+\S+\s+\S/';
$regex = '{@return(?:\s+'.TypeExpression::REGEX_TYPES.')?(?:\s+(?<description>(?!\*\/)\S+))?}sx';
}

if (Preg::match($regex, $annotation->getContent())) {
if (1 !== Preg::match($regex, $annotation->getContent(), $matches)) {
// Unable to match the annotation, it must be malformed or has unsupported format.
// Either way we don't want to tinker with it.
return false;
}

if (isset($matches['description'])) {
return false;
}

if (!isset($matches['types']) || '' === $matches['types']) {
// If there's no type info in the annotation, further checks make no sense, exit early.
return true;
}

$annotationTypes = $this->toComparableNames($annotation->getTypes(), $currentSymbol, $symbolShortNames);

if (['null'] === $annotationTypes) {
Expand Down
4 changes: 2 additions & 2 deletions tests/DocBlock/AnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ public function provideTypeParsingCases(): array
'/** @var array<Foo::A*>|null',
],
[
['null', 'true', 'false', '1', '1.5', "'a'", '"b"'],
'/** @var null|true|false|1|1.5|\'a\'|"b"',
['null', 'true', 'false', '1', '-1', '1.5', '-1.5', '.5', '1.', "'a'", '"b"'],
'/** @var null|true|false|1|-1|1.5|-1.5|.5|1.|\'a\'|"b"',
],
[
['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>'],
Expand Down
2 changes: 1 addition & 1 deletion tests/DocBlock/TypeExpressionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function provideGetTypesCases(): iterable

yield ['array<Foo::A*>|null', ['array<Foo::A*>', 'null']];

yield ['null|true|false|1|1.5|\'a\'|"b"', ['null', 'true', 'false', '1', '1.5', "'a'", '"b"']];
yield ['null|true|false|1|-1|1.5|-1.5|.5|1.|\'a\'|"b"', ['null', 'true', 'false', '1', '-1', '1.5', '-1.5', '.5', '1.', "'a'", '"b"']];

yield ['int | "a" | A<B<C, D>, E<F::*|G[]>>', ['int', '"a"', 'A<B<C, D>, E<F::*|G[]>>']];

Expand Down
181 changes: 181 additions & 0 deletions tests/Fixer/Phpdoc/NoSuperfluousPhpdocTagsFixerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,187 @@ public function bar(self $other, int $superfluous): self {}
public function bar(self $other, int $superfluous): self {}
};',
];

yield 'remove empty var' => [
'<?php
class Foo {
/**
*/
private $foo;
}',
'<?php
class Foo {
/**
* @var
*/
private $foo;
}',
];

yield 'remove empty var single line' => [
'<?php
class Foo {
/** */
private $foo;
}',
'<?php
class Foo {
/** @var */
private $foo;
}',
];

yield 'dont remove var without a type but with a property name and a description' => [
'<?php
class Foo {
/**
* @var $foo some description
*/
private $foo;
}',
];

yield 'dont remove single line var without a type but with a property name and a description' => [
'<?php
class Foo {
/** @var $foo some description */
private $foo;
}',
];

yield 'remove var without a type but with a property name' => [
'<?php
class Foo {
/**
*/
private $foo;
}',
'<?php
class Foo {
/**
* @var $foo
*/
private $foo;
}',
];

yield 'remove single line var without a type but with a property name' => [
'<?php
class Foo {
/** */
private $foo;
}',
'<?php
class Foo {
/** @var $foo */
private $foo;
}',
];

yield 'remove empty param' => [
'<?php
class Foo {
/**
*/
public function foo($foo) {}
}',
'<?php
class Foo {
/**
* @param
*/
public function foo($foo) {}
}',
];

yield 'remove empty single line param' => [
'<?php
class Foo {
/** */
public function foo($foo) {}
}',
'<?php
class Foo {
/** @param */
public function foo($foo) {}
}',
];

yield 'remove param without a type' => [
'<?php
class Foo {
/**
*/
public function foo($foo) {}
}',
'<?php
class Foo {
/**
* @param $foo
*/
public function foo($foo) {}
}',
];

yield 'remove single line param without a type' => [
'<?php
class Foo {
/** */
public function foo($foo) {}
}',
'<?php
class Foo {
/** @param $foo */
public function foo($foo) {}
}',
];

yield 'dont remove param without a type but with a description' => [
'<?php
class Foo {
/**
* @param $foo description
*/
public function foo($foo) {}
}',
];

yield 'dont remove single line param without a type but with a description' => [
'<?php
class Foo {
/** @param $foo description */
public function foo($foo) {}
}',
];

yield 'remove empty return' => [
'<?php
class Foo {
/**
*/
public function foo($foo) {}
}',
'<?php
class Foo {
/**
* @return
*/
public function foo($foo) {}
}',
];

yield 'remove empty single line return' => [
'<?php
class Foo {
/** */
public function foo($foo) {}
}',
'<?php
class Foo {
/** @return */
public function foo($foo) {}
}',
];
}

/**
Expand Down
1 change: 0 additions & 1 deletion tests/Fixtures/Integration/misc/PHP8_0.test
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class T
{
}

/** @return static */
public function something(): static
{
}
Expand Down

0 comments on commit 531eaeb

Please sign in to comment.