Skip to content

Commit

Permalink
Merge pull request #296 from sc-bruno/feature/deprecation-extension
Browse files Browse the repository at this point in the history
Add @deprecated tag support
  • Loading branch information
romalytvynenko authored Jan 16, 2024
2 parents a377018 + 13871e4 commit 9be8115
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/ScrambleServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Dedoc\Scramble\Support\InferExtensions\ResponseFactoryTypeInfer;
use Dedoc\Scramble\Support\InferExtensions\ValidatorTypeInfer;
use Dedoc\Scramble\Support\OperationBuilder;
use Dedoc\Scramble\Support\OperationExtensions\DeprecationExtension;
use Dedoc\Scramble\Support\OperationExtensions\ErrorResponsesExtension;
use Dedoc\Scramble\Support\OperationExtensions\RequestBodyExtension;
use Dedoc\Scramble\Support\OperationExtensions\RequestEssentialsExtension;
Expand Down Expand Up @@ -108,6 +109,7 @@ public function configurePackage(Package $package): void
RequestBodyExtension::class,
ErrorResponsesExtension::class,
ResponseExtension::class,
DeprecationExtension::class,
], $operationExtensions);
});

Expand Down
71 changes: 71 additions & 0 deletions src/Support/OperationExtensions/DeprecationExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

namespace Dedoc\Scramble\Support\OperationExtensions;

use Dedoc\Scramble\Extensions\OperationExtension;
use Dedoc\Scramble\Infer\Reflector\ClassReflector;
use Dedoc\Scramble\Support\Generator\Operation;
use Dedoc\Scramble\Support\PhpDoc;
use Dedoc\Scramble\Support\RouteInfo;
use Illuminate\Support\Str;
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;

/**
* Extension to add deprecation notice to the operation description
* Skips if method is not deprecated
* If a whole class is deprecated, all methods are deprecated, only add the description if exists
*/
class DeprecationExtension extends OperationExtension
{
public function handle(Operation $operation, RouteInfo $routeInfo)
{
// Skip if method is not deprecated
if (!$routeInfo->reflectionMethod() || $routeInfo->phpDoc()->getTagsByName('@not-deprecated')) {
return;
}

$fqdn = $routeInfo->reflectionMethod()->getDeclaringClass()->getName();
$deprecatedClass = $this->getClassDeprecatedValues($fqdn);
$deprecatedTags = $routeInfo->phpDoc()->getDeprecatedTagValues();

// Skip if no deprecations found
if (!$deprecatedClass && !$deprecatedTags) {
return;
}

$description = Str::of($this->generateDescription($deprecatedClass));

if ($description->isNotEmpty()) {
$description = $description->append("\n\n");
}

$description = $description->append($this->generateDescription($deprecatedTags));

$operation
->description((string) $description)
->deprecated(true);
}

/**
* @return array<DeprecatedTagValueNode>
*/
protected function getClassDeprecatedValues(string $fqdn)
{
$reflector = ClassReflector::make($fqdn);
$classPhpDocString = $reflector->getReflection()->getDocComment();

if ($classPhpDocString === false) {
return [];
}

return PhpDoc::parse($classPhpDocString)->getDeprecatedTagValues();
}

/**
* @return string
*/
private function generateDescription(array $deprecation)
{
return implode("\n", array_map(fn($tag) => $tag->description, $deprecation));
}
}
114 changes: 114 additions & 0 deletions tests/Support/OperationExtensions/DeprecationExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

use Illuminate\Support\Facades\Route as RouteFacade;

it('deprecated method sets deprecation key', function () {
$openApiDocument = generateForRoute(function () {
return RouteFacade::get('api/test', [Deprecated_ResponseExtensionTest_Controller::class, 'deprecated']);
});

expect($openApiDocument['paths']['/test']['get'])
->not()->toHaveKey('description')
->toHaveKey('deprecated', true);
});
class Deprecated_ResponseExtensionTest_Controller
{
/**
* @deprecated
*/
public function deprecated()
{
return false;
}
}

it('deprecated method sets key and description', function () {
$openApiDocument = generateForRoute(function () {
return RouteFacade::get('api/test', [Deprecated_Description_ResponseExtensionTest_Controller::class, 'deprecated']);
});

expect($openApiDocument['paths']['/test']['get'])
->toHaveKey('description', 'Deprecation description')
->toHaveKey('deprecated', true);
});

class Deprecated_Description_ResponseExtensionTest_Controller
{
/**
* @deprecated Deprecation description
*
* @response array{ "test": "test"}
*/
public function deprecated()
{
return false;
}
}

it('deprecated class with description sets keys', function () {
$openApiDocument = generateForRoute(function () {
return RouteFacade::get('api/test', [Deprecated_Class_Description_ResponseExtensionTest_Controller::class, 'deprecated']);
});

expect($openApiDocument['paths']['/test']['get'])
->toHaveKey('description', 'Class description' . "\n\n" . 'Deprecation description')
->toHaveKey('deprecated', true);
});

/** @deprecated Class description */
class Deprecated_Class_Description_ResponseExtensionTest_Controller
{
/**
* @deprecated Deprecation description
*
* @response array{ "test": "test"}
*/
public function deprecated()
{
return false;
}
}

it('deprecated class without description sets keys', function () {
$openApiDocument = generateForRoute(function () {
return RouteFacade::get('api/test', [Deprecated_Class_ResponseExtensionTest_Controller::class, 'deprecated']);
});

expect($openApiDocument['paths']['/test']['get'])
->not()->toHaveKey('description')
->toHaveKey('deprecated', true);
});

/** @deprecated */
class Deprecated_Class_ResponseExtensionTest_Controller
{
/**
* @response array{ "test": "test"}
*/
public function deprecated()
{
return false;
}
}

it('not deprecated ignores the class deprecation', function () {
$openApiDocument = generateForRoute(function () {
return RouteFacade::get('api/test', [Not_Deprecated_Class_ResponseExtensionTest_Controller::class, 'notDeprecated']);
});

expect($openApiDocument['paths']['/test']['get'])
->not()->toHaveKey('description')
->not()->toHaveKey('deprecated');
});

/** @deprecated */
class Not_Deprecated_Class_ResponseExtensionTest_Controller
{
/**
* @not-deprecated
*/
public function notDeprecated()
{
return false;
}
}

0 comments on commit 9be8115

Please sign in to comment.