Skip to content

Commit

Permalink
Support of phpstan/phpdoc-parser 1.15
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Dec 13, 2022
1 parent 32508da commit 326d195
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 14 deletions.
23 changes: 22 additions & 1 deletion SlevomatCodingStandard/Helpers/Annotation/MethodAnnotation.php
Expand Up @@ -5,6 +5,7 @@
use InvalidArgumentException;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
Expand Down Expand Up @@ -81,6 +82,16 @@ public function getMethodReturnType(): ?TypeNode
return $type;
}

/**
* @return TemplateTagValueNode[]
*/
public function getMethodTemplateTypes(): array
{
$this->errorWhenInvalid();

return $this->contentNode->templateTypes;
}

/**
* @return MethodTagValueParameterNode[]
*/
Expand All @@ -98,6 +109,8 @@ public function export(): string
? sprintf('%s ', AnnotationTypeHelper::export($this->getMethodReturnType()))
: '';

$templateTypes = $this->contentNode->templateTypes !== [] ? '<' . implode(', ', $this->contentNode->templateTypes) . '>' : '';

$parameters = [];
foreach ($this->getMethodParameters() as $parameter) {
$type = $parameter->type !== null ? AnnotationTypeHelper::export($parameter->type) . ' ' : '';
Expand All @@ -108,7 +121,15 @@ public function export(): string
$parameters[] = sprintf('%s%s%s%s%s', $type, $isReference, $isVariadic, $parameter->parameterName, $default);
}

$exported = sprintf('%s %s%s%s(%s)', $this->name, $static, $returnType, $this->getMethodName(), implode(', ', $parameters));
$exported = sprintf(
'%s %s%s%s%s(%s)',
$this->name,
$static,
$returnType,
$this->getMethodName(),
$templateTypes,
implode(', ', $parameters)
);

$description = $this->getDescription();
if ($description !== null) {
Expand Down
42 changes: 35 additions & 7 deletions SlevomatCodingStandard/Helpers/AnnotationHelper.php
Expand Up @@ -8,6 +8,7 @@
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode;
use PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode;
Expand Down Expand Up @@ -138,6 +139,14 @@ public static function getAnnotationTypes(Annotation $annotation): array
if ($annotation->getMethodReturnType() !== null) {
$annotationTypes[] = $annotation->getMethodReturnType();
}
foreach ($annotation->getMethodTemplateTypes() as $methodTemplateType) {
if ($methodTemplateType->bound !== null) {
$annotationTypes[] = $methodTemplateType->bound;
}
if ($methodTemplateType->default !== null) {
$annotationTypes[] = $methodTemplateType->default;
}
}
foreach ($annotation->getMethodParameters() as $methodParameterAnnotation) {
if ($methodParameterAnnotation->type === null) {
continue;
Expand Down Expand Up @@ -515,6 +524,13 @@ private static function fixAnnotation(Annotation $annotation, TypeNode $typeNode
if ($fixedContentNode->returnType !== null) {
$fixedContentNode->returnType = AnnotationTypeHelper::change($fixedContentNode->returnType, $typeNode, $fixedTypeNode);
}
foreach ($fixedContentNode->templateTypes as $templateTypeNo => $templateTypeNode) {
$fixedContentNode->templateTypes[$templateTypeNo] = self::fixTemplateTagValueNode(
$templateTypeNode,
$typeNode,
$fixedTypeNode
);
}
foreach ($fixedContentNode->parameters as $parameterNo => $parameterNode) {
if ($parameterNode->type === null) {
continue;
Expand All @@ -528,13 +544,7 @@ private static function fixAnnotation(Annotation $annotation, TypeNode $typeNode
);
}
} elseif ($annotation instanceof TemplateAnnotation) {
$fixedContentNode = clone $annotation->getContentNode();
if ($fixedContentNode->bound !== null) {
$fixedContentNode->bound = AnnotationTypeHelper::change($annotation->getBound(), $typeNode, $fixedTypeNode);
}
if ($fixedContentNode->default !== null) {
$fixedContentNode->default = AnnotationTypeHelper::change($annotation->getDefault(), $typeNode, $fixedTypeNode);
}
$fixedContentNode = self::fixTemplateTagValueNode($annotation->getContentNode(), $typeNode, $fixedTypeNode);
} elseif ($annotation instanceof TypeImportAnnotation) {
$fixedContentNode = clone $annotation->getContentNode();
/** @var IdentifierTypeNode $fixedType */
Expand Down Expand Up @@ -565,6 +575,24 @@ private static function fixAnnotation(Annotation $annotation, TypeNode $typeNode
);
}

private static function fixTemplateTagValueNode(
TemplateTagValueNode $node,
TypeNode $typeNode,
TypeNode $fixedTypeNode
): TemplateTagValueNode
{
$fixedNode = clone $node;

if ($fixedNode->bound !== null) {
$fixedNode->bound = AnnotationTypeHelper::change($node->bound, $typeNode, $fixedTypeNode);
}
if ($fixedNode->default !== null) {
$fixedNode->default = AnnotationTypeHelper::change($node->default, $typeNode, $fixedTypeNode);
}

return $fixedNode;
}

private static function fix(File $phpcsFile, Annotation $annotation, Annotation $fixedAnnotation): string
{
$spaceAfterContent = '';
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -18,7 +18,7 @@
"require": {
"php": "^7.2 || ^8.0",
"dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7",
"phpstan/phpdoc-parser": ">=1.14.0 <1.15.0",
"phpstan/phpdoc-parser": ">=1.15.0 <1.16.0",
"squizlabs/php_codesniffer": "^3.7.1"
},
"require-dev": {
Expand Down
24 changes: 20 additions & 4 deletions tests/Helpers/Annotation/MethodAnnotationTest.php
Expand Up @@ -6,6 +6,7 @@
use LogicException;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use SlevomatCodingStandard\Helpers\TestCase;

Expand All @@ -18,28 +19,35 @@ public function testAnnotation(): void
'@method',
1,
10,
'string method(int $p) Description',
'string method<T1, T2 of Bar, T3 of Baz>(int $p) Description',
new MethodTagValueNode(
false,
new IdentifierTypeNode('string'),
'method',
[new MethodTagValueParameterNode(new IdentifierTypeNode('int'), false, false, '$p', null)],
'Description'
'Description',
[
new TemplateTagValueNode('T1', null, ''),
new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''),
new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
]
)
);

self::assertSame('@method', $annotation->getName());
self::assertSame(1, $annotation->getStartPointer());
self::assertSame(10, $annotation->getEndPointer());
self::assertSame('string method(int $p) Description', $annotation->getContent());
self::assertSame('string method<T1, T2 of Bar, T3 of Baz>(int $p) Description', $annotation->getContent());

self::assertFalse($annotation->isInvalid());
self::assertTrue($annotation->hasDescription());
self::assertSame('Description', $annotation->getDescription());
self::assertSame('method', $annotation->getMethodName());
self::assertInstanceOf(IdentifierTypeNode::class, $annotation->getMethodReturnType());
self::assertCount(3, $annotation->getMethodTemplateTypes());
self::assertContainsOnlyInstancesOf(TemplateTagValueNode::class, $annotation->getMethodTemplateTypes());
self::assertCount(1, $annotation->getMethodParameters());
self::assertSame('@method string method(int $p) Description', $annotation->export());
self::assertSame('@method string method<T1, T2 of Bar, T3 of Baz>(int $p) Description', $annotation->export());
}

public function testUnsupportedAnnotation(): void
Expand Down Expand Up @@ -81,6 +89,14 @@ public function testGetMethodReturnTypeWhenInvalid(): void
$annotation->getMethodReturnType();
}

public function testGetMethodTemplateTypesWhenInvalid(): void
{
self::expectException(LogicException::class);
self::expectExceptionMessage('Invalid @method annotation.');
$annotation = new MethodAnnotation('@method', 1, 1, null, null);
$annotation->getMethodTemplateTypes();
}

public function testGetMethodParametersWhenInvalid(): void
{
self::expectException(LogicException::class);
Expand Down
15 changes: 14 additions & 1 deletion tests/Sniffs/Namespaces/ReferenceUsedNamesOnlySniffTest.php
Expand Up @@ -739,7 +739,7 @@ public function testSearchingInAnnotations(): void
]
);

self::assertSame(59, $report->getErrorCount());
self::assertSame(61, $report->getErrorCount());

self::assertSniffError(
$report,
Expand Down Expand Up @@ -1085,6 +1085,19 @@ public function testSearchingInAnnotations(): void
'Class \Foo\Something should not be referenced via a fully qualified name, but via a use statement.'
);

self::assertSniffError(
$report,
237,
ReferenceUsedNamesOnlySniff::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME,
'Class \Foo\Anything should not be referenced via a fully qualified name, but via a use statement.'
);
self::assertSniffError(
$report,
237,
ReferenceUsedNamesOnlySniff::CODE_REFERENCE_VIA_FULLY_QUALIFIED_NAME,
'Class \Foo\Something should not be referenced via a fully qualified name, but via a use statement.'
);

self::assertAllFixedInFile($report);
}

Expand Down
Expand Up @@ -253,3 +253,10 @@ public function covariant($parameter)
{
}
}

/**
* @method static bool compare<T1, T2 of Anything, T3 = Something>(T1 $t1, T2 $t2, T3 $t3)
*/
class MethodWithGenerics
{
}
Expand Up @@ -232,3 +232,10 @@ public function covariant($parameter)
{
}
}

/**
* @method static bool compare<T1, T2 of \Foo\Anything, T3 = \Foo\Something>(T1 $t1, T2 $t2, T3 $t3)
*/
class MethodWithGenerics
{
}

0 comments on commit 326d195

Please sign in to comment.