Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dev/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"twig/twig": "^3.5",
"phpstan/phpstan": "^2.0",
"symfony/finder": "^6.2",
"nikic/php-parser": "v5.7.0"
"nikic/php-parser": "v5.7.0",
"kcs/class-finder": "^0.6.1"
},
"autoload": {
"psr-4": {
Expand Down
36 changes: 36 additions & 0 deletions dev/src/DocFx/Node/ClassNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,4 +258,40 @@ public function setTocName(string $tocName)
{
$this->tocName = $tocName;
}

public function excludeFromDocs(): bool
{
// Skip the protobuf classes with underscores, they're all deprecated
// @TODO: Do not generate them in V2
if (false !== strpos($this->getName(), '_')) {
return true;
}

// Skip deprecated classes
if ('deprecated' === $this->getStatus()) {
return true;
}

// Manually skip GAPIC base clients
if ($this->isServiceBaseClass()) {

return true;
}

// Skip internal classes
if ($this->isInternal()) {
return true;
}

// Manually skip Grpc classes
// @TODO: remove this once we no longer have V2 classes
if (
'GrpcClient' === substr($this->getFullName(), -10)
&& '\Grpc\BaseStub' === $this->getExtends()
) {
return true;
}

return false;
}
}
72 changes: 72 additions & 0 deletions dev/src/DocFx/Node/InterfaceNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php
/**
* Copyright 2024 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Cloud\Dev\DocFx\Node;

use Kcs\ClassFinder\Finder\ComposerFinder;
use SimpleXMLElement;

/**
* @internal
*/
class InterfaceNode extends ClassNode
{
private array $implementingClasses = [];

public function __construct(
private SimpleXMLElement $xmlNode,
private array $protoPackages = [],
) {
parent::__construct($xmlNode, $protoPackages);
}

public function determineImplementingClasses(array $pageNodes): void
{
// Project root components
$componentDirs = array_map('realpath', glob(__DIR__ . '/../../../../*/src', GLOB_ONLYDIR));
$componentDirs[] = realpath(__DIR__ . '/../../../vendor/google/auth');
$componentDirs[] = realpath(__DIR__ . '/../../../vendor/google/gax');

$finder = new ComposerFinder();
$finder
->in($componentDirs)
->implementationOf($this->getFullName());

foreach ($finder as $className => $reflection) {
// ensure the class is part of our published documentation
if (isset($pageNodes['\\' . $className])) {
$this->implementingClasses[] = '\\' . $className;
}
}
}

public function getLongDescription(): string
{
$longDescription = parent::getLongDescription();
if (empty($this->implementingClasses)) {
return $longDescription;
}
$longDescription .= empty($longDescription) ? '' : "\n";
$longDescription .= 'Classes which implement this interface in this package:';

foreach ($this->implementingClasses as $className) {
$longDescription .= sprintf("\n - {@see %s}", $className);
}

return $longDescription;
}
}
82 changes: 31 additions & 51 deletions dev/src/DocFx/Page/PageTree.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
namespace Google\Cloud\Dev\DocFx\Page;

use Google\Cloud\Dev\DocFx\Node\ClassNode;
use Google\Cloud\Dev\DocFx\Node\InterfaceNode;
use Google\Cloud\Dev\DocFx\Toc\NamespaceToc;
use SimpleXMLElement;

Expand Down Expand Up @@ -54,66 +55,49 @@ private function loadPages(): array

// List of pages, to sort alphabetically by key
$pageMap = [];

$isDiregapic = false;

$gapicClients = [];
$interfaces = [];

// Build the list of pages we are going to generate documentation for
foreach ($structure->file as $file) {
// only document classes for now
if (!isset($file->class[0])) {
if (!isset($file->class[0]) && !isset($file->interface[0])) {
// only document classes and interfaces for now
continue;
}

$classNode = new ClassNode($file->class[0], $this->componentPackages);
$node = isset($file->class[0])
? new ClassNode($file->class[0], $this->componentPackages)
: new InterfaceNode($file->interface[0], $this->componentPackages);

// Skip the protobuf classes with underscores, they're all deprecated
// @TODO: Do not generate them in V2
if (false !== strpos($classNode->getName(), '_')) {
continue;
if ($node instanceof InterfaceNode) {
$interfaces[] = $node;
}

// Skip deprecated classes
if ('deprecated' === $classNode->getStatus()) {
continue;
}

// Manually skip GAPIC base clients
if ($classNode->isServiceBaseClass()) {
$gapicClients[] = $classNode;
continue;
}

// Skip internal classes
if ($classNode->isInternal()) {
if ($node->excludeFromDocs()) {
// Keep track of base clients
if ($node->isServiceBaseClass()) {
$gapicClients[] = $node;
}
continue;
}

$this->hasV1Client |= $classNode->isV1ServiceClass();
$this->hasV2Client |= $classNode->isV2ServiceClass();
$this->hasV1Client |= $node->isV1ServiceClass();
$this->hasV2Client |= $node->isV2ServiceClass();

// Manually skip protobuf enums in favor of Gapic enums (see below).
// @TODO: Do not generate them in V2, eventually mark them as deprecated
$isDiregapic = $isDiregapic || $classNode->isGapicEnumClass();

// Manually skip Grpc classes
// @TODO: Do not generate Grpc classes in V2, eventually mark these as deprecated
$fullName = $classNode->getFullname();
if (
'GrpcClient' === substr($fullName, -10)
&& '\Grpc\BaseStub' === $classNode->getExtends()
) {
continue;
}
$isDiregapic |= $node->isGapicEnumClass();

$fullName = $node->getFullname();

// Skip classes that are in the structure.xml but not a part of this namespace
if (0 !== strpos($fullName, '\\' . $this->namespace)) {
continue;
}

$pageMap[$fullName] = new Page(
$classNode,
$node,
$file['path'],
$this->packageDescription,
$this->componentPath
Expand All @@ -132,27 +116,23 @@ private function loadPages(): array
}
}

/**
* Determine all classes which implement an interface in this library,
* so that we can list them in the interface's reference documentation.
*/
foreach ($interfaces as $interface) {
$interface->determineImplementingClasses($pageMap);
}

// Combine Client classes with internal Gapic\Client
$pageMap = $this->combineGapicClients($gapicClients, $pageMap);

// Sort pages alphabetically by full class name
ksort($pageMap);

// We no longer need the array keys
$pages = array_values($pageMap);

// Mark V2 services as "beta" if they have a V1 client
if ($this->hasV1Client && $this->hasV2Client) {
foreach ($pages as $page) {
if ($page->getClassNode()->isV2ServiceClass()) {
$page->getClassNode()->setTocName(sprintf(
'%s (beta)',
$page->getClassNode()->getName()
));
}
}
}

// Sort pages alphabetically by full class name
ksort($pages);

return $pages;
}

Expand Down
4 changes: 2 additions & 2 deletions dev/tests/Unit/Command/DocFxCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function testGenerateVisionStructureXml()
$componentDir = __DIR__ . '/../../../../Vision';
$process = DocFxCommand::getPhpDocCommand($componentDir, self::$tmpDir);
$process->mustRun();
$left = self::$fixturesDir . '/phpdoc/structure.xml';
$left = self::$fixturesDir . '/phpdoc/vision.xml';
$right = self::$tmpDir . '/structure.xml';

$this->assertFileEqualsWithDiff($left, $right, '1' === getenv('UPDATE_FIXTURES'));
Expand Down Expand Up @@ -138,7 +138,7 @@ public function provideDocFxFiles()
{
$output = self::getCommandTester()->execute([
'--component' => 'Vision',
'--xml' => self::$fixturesDir . '/phpdoc/structure.xml',
'--xml' => self::$fixturesDir . '/phpdoc/vision.xml',
'--out' => self::$tmpDir = sys_get_temp_dir() . '/' . rand(),
'--metadata-version' => '1.0.0',
'--path' => self::$fixturesDir . '/component/Vision',
Expand Down
35 changes: 34 additions & 1 deletion dev/tests/Unit/DocFx/PageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

namespace Google\Cloud\Dev\Tests\Unit\DocFx;

use Google\Auth\Credentials\ServiceAccountCredentials;
use Google\Auth\FetchAuthTokenInterface;
use PHPUnit\Framework\TestCase;
use Google\Cloud\Dev\DocFx\Node\ClassNode;
use Google\Cloud\Dev\DocFx\Node\InterfaceNode;
use Google\Cloud\Dev\DocFx\Page\OverviewPage;
use Google\Cloud\Dev\DocFx\Page\Page;
use Google\Cloud\Dev\DocFx\Page\PageTree;
Expand Down Expand Up @@ -65,7 +68,7 @@ public function provideFriendlyApiName()

public function testLoadPagesProtoPackages()
{
$structureXml = __DIR__ . '/../../fixtures/phpdoc/structure.xml';
$structureXml = __DIR__ . '/../../fixtures/phpdoc/vision.xml';
$componentPath = __DIR__ . '/../../fixtures/component/Vision';
$protoPackages = [
'google.longrunning' => 'Google\LongRunning',
Expand Down Expand Up @@ -109,6 +112,36 @@ public function testOverviewPage()
$this->assertStringEndsWith("\nend.", $overview4->getContents());
}

public function testInterfacePage()
{
$structureXml = __DIR__ . '/../../fixtures/phpdoc/auth.xml';
$componentPath = __DIR__ . '/../../../vendor/google/auth';
$protoPackages = [];
$pageTree = new PageTree(
$structureXml,
'Google\Auth',
'Google Auth',
$componentPath,
$protoPackages
);

$pages = $pageTree->getPages();
$interfacePage = null;
foreach ($pages as $page) {
if (ltrim($page->getClassNode()->getFullname(), '\\') === FetchAuthTokenInterface::class) {
$interfacePage = $page;
break;
}
}

$this->assertNotNull($interfacePage);
$description = $interfacePage->getItems()[0]['summary'] ?? '';
$this->assertStringContainsString(
ServiceAccountCredentials::class,
$description
);
}

public function testHandleSample()
{
$structureXml = __DIR__ . '/../../fixtures/phpdoc/clientsnippets.xml';
Expand Down
8 changes: 4 additions & 4 deletions dev/tests/fixtures/docfx/NewClient/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
uid: 'services:Google\Cloud\SecretManager\V1'
items:
-
uid: \Google\Cloud\SecretManager\V1\SecretManagerServiceClient
uid: \Google\Cloud\SecretManager\V1\Client\SecretManagerServiceClient
name: SecretManagerServiceClient
-
uid: \Google\Cloud\SecretManager\V1\Client\SecretManagerServiceClient
name: 'SecretManagerServiceClient (beta)'
uid: \Google\Cloud\SecretManager\V1\SecretManagerServiceClient
name: SecretManagerServiceClient
-
name: V1beta1
uid: 'ns:Google\Cloud\SecretManager\V1beta1'
Expand All @@ -44,5 +44,5 @@
items:
-
uid: \Google\Cloud\SecretManager\V1beta2\Client\SecretManagerServiceClient
name: 'SecretManagerServiceClient (beta)'
name: SecretManagerServiceClient
status: beta
6 changes: 3 additions & 3 deletions dev/tests/fixtures/docfx/Vision/V1.AnnotateFileRequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ items:
description: 'Additional context that may accompany the image(s) in the file.'
-
id: '↳ pages'
var_type: array<int>
var_type: 'int[]'
description: 'Pages of the file to perform image annotation. Pages starts from 1, we assume the first page of the file is page 1. At most 5 pages are supported per request. Pages can be negative. Page 1 means the first page. Page 2 means the second page. Page -1 means the last page. Page -2 means the second to the last page. If the file is GIF instead of PDF or TIFF, page refers to GIF frames. If this field is empty, by default the service performs image annotation for the first 5 pages of the file.'
-
uid: '\Google\Cloud\Vision\V1\AnnotateFileRequest::getInputConfig()'
Expand Down Expand Up @@ -117,7 +117,7 @@ items:
syntax:
returns:
-
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/Internal/RepeatedField">Google\Protobuf\Internal\RepeatedField</a>'
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/RepeatedField">Google\Protobuf\RepeatedField</a>&lt;<xref uid="\Google\Cloud\Vision\V1\Feature">Feature</xref>&gt;'
-
uid: '\Google\Cloud\Vision\V1\AnnotateFileRequest::setFeatures()'
name: setFeatures
Expand Down Expand Up @@ -206,7 +206,7 @@ items:
syntax:
returns:
-
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/Internal/RepeatedField">Google\Protobuf\Internal\RepeatedField</a>'
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/RepeatedField">Google\Protobuf\RepeatedField</a>&lt;<xref uid="int">int</xref>&gt;'
-
uid: '\Google\Cloud\Vision\V1\AnnotateFileRequest::setPages()'
name: setPages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ items:
syntax:
returns:
-
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/Internal/RepeatedField">Google\Protobuf\Internal\RepeatedField</a>'
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/RepeatedField">Google\Protobuf\RepeatedField</a>&lt;<xref uid="\Google\Cloud\Vision\V1\AnnotateImageResponse">AnnotateImageResponse</xref>&gt;'
-
uid: '\Google\Cloud\Vision\V1\AnnotateFileResponse::setResponses()'
name: setResponses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ items:
syntax:
returns:
-
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/Internal/RepeatedField">Google\Protobuf\Internal\RepeatedField</a>'
var_type: '<a href="https://protobuf.dev/reference/php/api-docs/Google/Protobuf/RepeatedField">Google\Protobuf\RepeatedField</a>&lt;<xref uid="\Google\Cloud\Vision\V1\Feature">Feature</xref>&gt;'
-
uid: '\Google\Cloud\Vision\V1\AnnotateImageRequest::setFeatures()'
name: setFeatures
Expand Down
Loading
Loading