Skip to content

Commit f3eae98

Browse files
committed
Latest changes after annotating the latest plugins
1 parent 9a8dd16 commit f3eae98

3 files changed

Lines changed: 150 additions & 14 deletions

File tree

Annotations/AnnotationGenerator.php

Lines changed: 135 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,34 @@ class AnnotationGenerator
3131
{
3232
public const EXAMPLE_CHAR_LIMIT = 3000;
3333

34+
public const GLOBAL_PARAMETER_NAMES = [
35+
'idSite',
36+
'period',
37+
'date',
38+
'segment',
39+
'expanded',
40+
'idSubtable',
41+
'flat',
42+
'filter_pattern',
43+
'filter_column',
44+
'filter_pattern_recursive',
45+
'filter_column_recursive',
46+
'filter_excludelowpop',
47+
'filter_excludelowpop_value',
48+
'filter_sort_column',
49+
'filter_sort_order',
50+
'filter_truncate',
51+
'filter_limit',
52+
'filter_offset',
53+
'keep_summary_row',
54+
'disable_generic_filters',
55+
'disable_queued_filters',
56+
'hideColumns',
57+
'showColumns',
58+
'label',
59+
'idGoal',
60+
];
61+
3462
/**
3563
* @var DocumentationGenerator
3664
*/
@@ -41,9 +69,15 @@ class AnnotationGenerator
4169
*/
4270
protected $reportMetadata;
4371

72+
/**
73+
* @var array[]
74+
*/
75+
protected $missingImportantDataWarnings;
76+
4477
public function __construct(DocumentationGenerator $generator)
4578
{
4679
$this->generator = $generator;
80+
$this->missingImportantDataWarnings = [];
4781
}
4882

4983
/**
@@ -65,7 +99,7 @@ public function __construct(DocumentationGenerator $generator)
6599
*/
66100
public function generatePluginApiAnnotations(string $pluginName, bool $writeToFile = false, bool $useTmpDir = false): array
67101
{
68-
BaseValidator::check('plugin', $pluginName, [ new NotEmpty() ]);
102+
BaseValidator::check('plugin', $pluginName, [new NotEmpty()]);
69103
Manager::getInstance()->checkIsPluginActivated($pluginName);
70104

71105
$currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs');
@@ -107,7 +141,28 @@ public function generatePluginApiAnnotations(string $pluginName, bool $writeToFi
107141
$this->writeAnnotationsToFile($annotations, $pluginAnnotationPath, $pluginName);
108142
}
109143

110-
return $annotations;
144+
if (count($this->missingImportantDataWarnings) === 0) {
145+
return $annotations;
146+
}
147+
148+
$lines = [];
149+
foreach ($this->missingImportantDataWarnings as $methodName => $warnings) {
150+
if (empty($warnings)) {
151+
continue;
152+
}
153+
154+
$lines[] = $methodName . ' has the following warnings:';
155+
foreach ($warnings as $paramName => $warningLines) {
156+
if (empty($warningLines)) {
157+
continue;
158+
}
159+
160+
$lines[] = '- ' . $paramName . ':';
161+
$lines[] = " - " . implode("\n - ", $warningLines);
162+
}
163+
}
164+
165+
return $lines;
111166
}
112167

113168
/**
@@ -215,18 +270,18 @@ protected function buildAnnotationForMethod(array $rules, string $pluginName, \R
215270
*/
216271
public function getParamInfoFromDocBlock(string $docBlock): array
217272
{
218-
$lexer = new Lexer();
273+
$lexer = new Lexer();
219274
$tokens = $lexer->tokenize($docBlock);
220275
$expressionParser = new ConstExprParser();
221276
$parser = new PhpDocParser(new TypeParser($expressionParser), $expressionParser);
222-
$node = $parser->parse(new TokenIterator($tokens));
277+
$node = $parser->parse(new TokenIterator($tokens));
223278

224279
$params = [];
225280
foreach ($node->getParamTagValues() as $param) {
226281
$name = ltrim($param->parameterName, '$');
227282
$params[$name] = [
228-
'type' => (string) $param->type,
229-
// Normalise the description. E.g. remove linebreaks and indentation
283+
'type' => (string)$param->type,
284+
// Normalise the description. E.g. remove linebreaks and indentation
230285
'description' => trim(preg_replace(['/^\h+/m', '/\R+/u',], ['', ' '], $param->description)),
231286
'byRef' => $param->isReference,
232287
'variadic' => $param->isVariadic,
@@ -246,11 +301,11 @@ public function getParamInfoFromDocBlock(string $docBlock): array
246301
*/
247302
public function getResponseInfoFromDocBlock(string $docBlock): array
248303
{
249-
$lexer = new Lexer();
304+
$lexer = new Lexer();
250305
$tokens = $lexer->tokenize($docBlock);
251306
$expressionParser = new ConstExprParser();
252307
$parser = new PhpDocParser(new TypeParser($expressionParser), $expressionParser);
253-
$node = $parser->parse(new TokenIterator($tokens));
308+
$node = $parser->parse(new TokenIterator($tokens));
254309

255310
$responseInfo = ['type' => null];
256311
$returnTags = $node->getReturnTagValues();
@@ -293,6 +348,7 @@ public function buildVirtualPath(string $virtualPathTemplate, string $plugin, st
293348
* Build the key data for the specified parameter. This should be all the data necessary to create an OA\Parameter
294349
* annotation object.
295350
*
351+
* @param string $methodName The name of the method. E.g. getAlert
296352
* @param string $paramName The name of the parameter. E.g. idSite or period
297353
* @param array $paramMetadata The collection of metadata from the old DocumentationGenerator class. Things like
298354
* whether the parameter is typed, is required, or has a default value.
@@ -312,9 +368,12 @@ public function buildVirtualPath(string $virtualPathTemplate, string $plugin, st
312368
* 'example' => 1,
313369
* ]
314370
*/
315-
public function buildParameterAnnotationData(string $paramName, array $paramMetadata, array $paramDocInfo): array
371+
public function buildParameterAnnotationData(string $methodName, string $paramName, array $paramMetadata, array $paramDocInfo): array
316372
{
317373
$docType = strtolower(trim($paramDocInfo['type'] ?? ''));
374+
if (empty($docType)) {
375+
$this->addMissingImportantDataWarning($methodName, $paramName, 'Type is not specified in comment block.');
376+
}
318377
$metaType = strtolower(trim($paramMetadata['type'] ?? $docType));
319378
$type = $metaType === 'string' && $docType !== 'string' ? $docType : $metaType;
320379
// If the signature type is array, but the type hinting provides more, use that instead
@@ -336,6 +395,9 @@ public function buildParameterAnnotationData(string $paramName, array $paramMeta
336395

337396
$isRequired = !key_exists('default', $paramMetadata) || $paramMetadata['default'] instanceof NoDefaultValue;
338397
$description = $paramDocInfo['description'] ?? '';
398+
if (empty($description)) {
399+
$this->addMissingImportantDataWarning($methodName, $paramName, 'Description is not specified in comment block.');
400+
}
339401
$example = '';
340402
// Check the description for the example value
341403
if (preg_match('/\[@example\s*=\s*([^\n]+)\]/', $description, $m)) {
@@ -349,6 +411,10 @@ public function buildParameterAnnotationData(string $paramName, array $paramMeta
349411
$example = trim($example, '"');
350412
}
351413

414+
// Clean up the descriptions a little more like removing linebreaks and escaping double-quotes
415+
$description = str_replace("\n", ' ', $description);
416+
$description = str_replace('"', '""', $description);
417+
352418
return [
353419
'name' => $paramName,
354420
'types' => $typesMap,
@@ -359,6 +425,48 @@ public function buildParameterAnnotationData(string $paramName, array $paramMeta
359425
];
360426
}
361427

428+
/**
429+
* Add an entry to the map of warnings about missing important information, like type and description of parameters
430+
* and returns.
431+
*
432+
* @param string $methodName Name of the method to more easily identify where in the code needs adjustment.
433+
* @param string $paramName Name of the parameter or "return" for the response. E.g. idSite, period, return, ...
434+
* @param string $message Message indicating what is missing. E.g. "Type is not specified in comment block."
435+
*
436+
* @return void
437+
*/
438+
protected function addMissingImportantDataWarning(string $methodName, string $paramName, string $message): void
439+
{
440+
// Make sure that the inner arrays have been initialised and then add the message to the warning map
441+
$this->missingImportantDataWarnings[$methodName] = $this->missingImportantDataWarnings[$methodName] ?? [];
442+
$this->missingImportantDataWarnings[$methodName][$paramName] = $this->missingImportantDataWarnings[$methodName][$paramName] ?? [];
443+
$this->missingImportantDataWarnings[$methodName][$paramName][] = $message;
444+
}
445+
446+
/**
447+
* Remove a warning from the collection. This is useful when it's determined after the fact that a parameter has
448+
* a global component which can be used, like idSite or period.
449+
*
450+
* @param string $methodName Name of the method.
451+
* @param string $paramName Name of the parameter or "return" for the response. E.g. idSite, period, return, ...
452+
*
453+
* @return void
454+
*/
455+
protected function removeMissingImportantDataWarning(string $methodName, string $paramName): void
456+
{
457+
if (empty($this->missingImportantDataWarnings[$methodName][$paramName])) {
458+
return;
459+
}
460+
461+
// If it's the only param in the collection for the method, remove the method
462+
if (count($this->missingImportantDataWarnings[$methodName]) === 1) {
463+
unset($this->missingImportantDataWarnings[$methodName]);
464+
return;
465+
}
466+
467+
unset($this->missingImportantDataWarnings[$methodName][$paramName]);
468+
}
469+
362470
/**
363471
* Build the collection of parameters and key information about them for the specified method.
364472
*
@@ -399,7 +507,16 @@ protected function determineParameters(array $rules, string $plugin, string $met
399507
continue;
400508
}
401509

402-
$customParams[] = $this->buildParameterAnnotationData($name, $paramMetadata, $paramInfo);
510+
// If the parameter doesn't have a description and matches a global, use a reference to the global instead.
511+
$customParamData = $this->buildParameterAnnotationData($method, $name, $paramMetadata, $paramInfo);
512+
if (empty($customParamData['description']) && in_array($name, self::GLOBAL_PARAMETER_NAMES)) {
513+
$globalParamSuffix = $customParamData['required'] === 'true' ? 'Required' : 'Optional';
514+
$refs[] = '#/components/parameters/' . $name . $globalParamSuffix;
515+
$this->removeMissingImportantDataWarning($method, $name);
516+
continue;
517+
}
518+
519+
$customParams[] = $customParamData;
403520
}
404521

405522
return [
@@ -415,6 +532,7 @@ protected function determineParameters(array $rules, string $plugin, string $met
415532
* @link https://spec.openapis.org/oas/v3.1.1.html#data-types
416533
*
417534
* @param string $type The PHP type from the method signature or doc-block
535+
*
418536
* @return string The normalised Data Type to be used in the swagger-php annotation
419537
*/
420538
public function getOpenApiTypeFromPhpType(string $type): string
@@ -812,6 +930,8 @@ protected function determineResponses(array $rules, string $plugin, string $meth
812930

813931
if (!empty($responseInfo['description'])) {
814932
$successArray['description'] = $responseInfo['description'];
933+
} elseif (empty($successArray['ref'])) {
934+
$this->addMissingImportantDataWarning($method, 'return', 'Description is not specified in comment block.');
815935
}
816936

817937
$responseSchema = !empty($responseInfo['type']) ? $this->buildSchemaObjectArray($responseInfo['type']) : [];
@@ -883,7 +1003,7 @@ protected function determineResponses(array $rules, string $plugin, string $meth
8831003
$successArray['description'] = '';
8841004
}
8851005
} else {
886-
// Make sure the schema is included in there are no examples
1006+
// Make sure the schema is included if there are no examples
8871007
$successArray['schema'] = $responseSchema;
8881008
}
8891009

@@ -897,6 +1017,10 @@ protected function determineResponses(array $rules, string $plugin, string $meth
8971017
// Append the links to the description with a prefix linebreak. If there's no description, skip the break
8981018
$successArray['description'] .= (!empty($successArray['description']) && !empty($descriptionLinks) ? '</br>' : '') . $descriptionLinks;
8991019

1020+
if (empty($successArray['ref']) && empty($descriptionLinks) && empty($successArray['schema'])) {
1021+
$this->addMissingImportantDataWarning($method, 'return', 'Type could not be determined via comment block or example.');
1022+
}
1023+
9001024
$responses[] = $successArray;
9011025

9021026
if (!empty($rules['defaultErrorResponseRefs'])) {

Annotations/GlobalApiComponents.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,15 @@
323323
* )
324324
*
325325
* Parameters specific to DataTables and Views
326+
* @OA\Parameter(parameter="expandedOptional", name="expanded", in="query",
327+
* description="If true, loads all subtables.", required=false,
328+
* @OA\Schema(type="integer", enum={0,1}, example=0, default=0))
329+
*
330+
* @OA\Parameter(parameter="idSubtableOptional", name="idSubtable", in="query",
331+
* description="An in-database subtable ID.", required=false,
332+
* @OA\Schema(type="integer"))
333+
*
334+
* Parameters specific to DataTables and Views
326335
* @OA\Parameter(parameter="flatOptional", name="flat", in="query",
327336
* description="Flatten subtables into the parent table.", required=false,
328337
* @OA\Schema(type="integer", enum={0,1}, example=0))
@@ -358,7 +367,7 @@
358367
* description="Row index after which rows are removed.", required=false, @OA\Schema(type="integer"))
359368
*
360369
* @OA\Parameter(parameter="filter_limitOptional", name="filter_limit", in="query",
361-
* description="Maximum rows to return.", required=false, @OA\Schema(type="integer"))
370+
* description="Maximum number of rows to return.", required=false, @OA\Schema(type="integer"))
362371
*
363372
* @OA\Parameter(parameter="filter_offsetOptional", name="filter_offset", in="query",
364373
* description="Row offset.", required=false, @OA\Schema(type="integer"))
@@ -384,8 +393,11 @@
384393
* @OA\Parameter(parameter="labelOptional", name="label", in="query",
385394
* description="Keep only rows with these label(s). Supports path via '>' and arrays.", required=false, @OA\Schema(type="string"))
386395
*
396+
* @OA\Parameter(parameter="idGoalRequired", name="idGoal", in="query",
397+
* description="The ID of a configured goal.", required=true, @OA\Schema(type="integer"))
398+
*
387399
* @OA\Parameter(parameter="idGoalOptional", name="idGoal", in="query",
388-
* description="Goal ID or special values (overview/minimal/full table).", required=false, @OA\Schema(type="string"))
400+
* description="The ID of a configured goal.", required=false, @OA\Schema(type="integer"))
389401
*/
390402
class GlobalApiComponents
391403
{

tests/Unit/AnnotationGeneratorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ public function getTestDataForBuildVirtualPath(): iterable
262262
*/
263263
public function testBuildParameterAnnotationData(string $paramName, array $paramMetadata, array $paramDocInfo, array $expected): void
264264
{
265-
$this->assertEquals($expected, $this->annotationGenerator->buildParameterAnnotationData($paramName, $paramMetadata, $paramDocInfo));
265+
$this->assertEquals($expected, $this->annotationGenerator->buildParameterAnnotationData('someMethodName', $paramName, $paramMetadata, $paramDocInfo));
266266
}
267267

268268
/**

0 commit comments

Comments
 (0)