From b1503033618656c38aa3602f72aa961d2ac27146 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 4 Oct 2023 11:36:57 -0700 Subject: [PATCH] move script into DocFX command for simplicity --- dev/src/Command/DocFxCommand.php | 108 +++++++++++++++++++++++++++++-- dev/src/DocFx/Node/ClassNode.php | 93 +++----------------------- 2 files changed, 114 insertions(+), 87 deletions(-) diff --git a/dev/src/Command/DocFxCommand.php b/dev/src/Command/DocFxCommand.php index 9df73d41c46f..c6542b202240 100644 --- a/dev/src/Command/DocFxCommand.php +++ b/dev/src/Command/DocFxCommand.php @@ -25,6 +25,9 @@ use Symfony\Component\Process\Process; use Symfony\Component\Yaml\Yaml; use RuntimeException; +use Google\Cloud\Core\Logger\AppEngineFlexFormatter; +use Google\Cloud\Core\Logger\AppEngineFlexFormatterV2; +use Google\Cloud\Dev\DocFx\Node\ClassNode; use Google\Cloud\Dev\DocFx\Page\PageTree; use Google\Cloud\Dev\DocFx\Page\OverviewPage; use Google\Cloud\Dev\Component; @@ -37,6 +40,8 @@ class DocFxCommand extends Command private array $composerJson; private array $repoMetadataJson; + private const XREF_REGEX = '/setName('docfx') @@ -95,6 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $valid = true; $tocItems = []; $packageDescription = $component->getDescription(); + $isBeta = 'stable' !== $component->getReleaseLevel(); foreach ($component->getNamespaces() as $namespace => $dir) { $pageTree = new PageTree( $xml, @@ -105,7 +111,7 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($pageTree->getPages() as $page) { // validate the docs page. this will fail the job if it's false - $page->getClassNode()->validate($output) && $valid; + $valid = $this->validate($page->getClassNode(), $output) && $valid; $docFxArray = ['items' => $page->getItems()]; // Dump the YAML for the class node @@ -125,11 +131,10 @@ protected function execute(InputInterface $input, OutputInterface $output) return 1; } - $releaseLevel = $component->getReleaseLevel(); if (file_exists($overviewFile = sprintf('%s/README.md', $component->getPath()))) { $overview = new OverviewPage( file_get_contents($overviewFile), - $releaseLevel !== 'stable' + $isBeta ); $outFile = sprintf('%s/%s', $outDir, $overview->getFilename()); file_put_contents($outFile, $overview->getContents()); @@ -141,7 +146,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $componentToc = array_filter([ 'uid' => $component->getReferenceDocumentationUid(), 'name' => $component->getPackageName(), - 'status' => $releaseLevel !== 'stable' ? 'beta' : '', + 'status' => $isBeta ? 'beta' : '', 'items' => $tocItems, ]); $tocYaml = Yaml::dump([$componentToc], $inline, $indent, $flags); @@ -207,4 +212,99 @@ public static function getPhpDocCommand(string $componentPath, string $outDir): return $process; } + + private function validate(ClassNode $class, OutputInterface $output): bool + { + $valid = true; + $hadWarning = false; + $isGenerated = $class->isProtobufMessageClass() || $class->isProtobufEnumClass() || $class->isServiceClass(); + $nodes = array_merge([$class], $class->getMethods(), $class->getConstants()); + foreach ($nodes as $node) { + foreach ($this->getInvalidXrefs($node->getContent()) as $invalidRef) { + $output->write(sprintf("\nInvalid xref in %s: %s", $node->getFullname(), $invalidRef)); + $valid = false; + $hadWarning = true; + } + foreach ($this->getBrokenXrefs($node->getContent()) as $brokenRef) { + $output->write(sprintf("\nBroken xref in %s: %s", $node->getFullname(), $brokenRef)); + $valid = $valid && (false || $isGenerated); + $hadWarning = true; + } + } + if ($hadWarning) { + $output->writeln(''); + } + return $valid; + } + + /** + * Verifies that protobuf references and @see tags are properly formatted. + */ + private function getInvalidXrefs(string $description): array + { + $invalidRefs = []; + preg_replace_callback( + self::XREF_REGEX, + function ($matches) use (&$invalidRefs) { + // Valid external reference + if (0 === strpos($matches[1], 'http')) { + return; + } + // Invalid reference format + if ('\\' !== $matches[1][0] || substr_count($matches[1], '\Google\\') > 1) { + $invalidRefs[] = $matches[1]; + } + }, + $description + ); + + return $invalidRefs; + } + + /** + * Verifies that protobuf references and @see tags reference classes that exist. + */ + private function getBrokenXrefs(string $description): array + { + $brokenRefs = []; + preg_replace_callback( + self::XREF_REGEX, + function ($matches) use (&$brokenRefs) { + if (0 === strpos($matches[1], 'http')) { + return; // Valid external reference + } + if (in_array( + $matches[1], + ['\\' . AppEngineFlexFormatter::class, '\\' . AppEngineFlexFormatterV2::class]) + ) { + return; // We cannot run "class_exists" on these because they will throw a fatal error. + } + if (class_exists($matches[1]) || interface_exists($matches[1] || trait_exists($matches[1]))) { + return; // Valid class reference + } + if (false !== strpos($matches[1], '::')) { + if (false !== strpos($matches[1], '()')) { + list($class, $method) = explode('::', str_replace('()', '', $matches[1])); + if (method_exists($class, $method)) { + return; // Valid method reference + } + if ('Async' === substr($method, -5)) { + return; // Skip magic Async methods + } + } elseif (defined($matches[1])) { + return; // Valid constant reference + } + } + // empty hrefs show up as "\\" + if ($matches[1] === '\\\\') { + $brokenRefs[] = 'empty'; + } else { + $brokenRefs[] = $matches[1]; + } + }, + $description + ); + + return $brokenRefs; + } } diff --git a/dev/src/DocFx/Node/ClassNode.php b/dev/src/DocFx/Node/ClassNode.php index ee11f17cfb3c..d2e787358e35 100644 --- a/dev/src/DocFx/Node/ClassNode.php +++ b/dev/src/DocFx/Node/ClassNode.php @@ -17,10 +17,7 @@ namespace Google\Cloud\Dev\DocFx\Node; -use Google\Cloud\Core\Logger\AppEngineFlexFormatter; -use Google\Cloud\Core\Logger\AppEngineFlexFormatterV2; use SimpleXMLElement; -use Symfony\Component\Console\Output\OutputInterface; /** * @internal @@ -174,16 +171,16 @@ public function getMethods(): array foreach ($this->xmlNode->method as $methodNode) { $method = new MethodNode($methodNode, $this->protoPackages); if ($method->isPublic() && !$method->isInherited() && !$method->isExcludedMethod()) { - // This is to fix an issue in phpdocumentor where magic methods do not have - // "inhereted_from" set as expected. - // TODO: Remove this once the above issue is fixed. - // @see https://github.com/phpDocumentor/phpDocumentor/pull/3520 - if (false !== strpos($method->getFullname(), 'Async()')) { - list($class, $_) = explode('::', $method->getFullname()); - if ($class !== $this->getFullName()) { - continue; - } - } + // // This is to fix an issue in phpdocumentor where magic methods do not have + // // "inhereted_from" set as expected. + // // TODO: Remove this once the above issue is fixed. + // // @see https://github.com/phpDocumentor/phpDocumentor/pull/3520 + // if (false !== strpos($method->getFullname(), 'Async()')) { + // list($class, $_) = explode('::', $method->getFullname()); + // if ($class !== $this->getFullName()) { + // continue; + // } + // } $methods[] = $method; } } @@ -263,74 +260,4 @@ public function setTocName(string $tocName) { $this->tocName = $tocName; } - - public function validate(OutputInterface $output): bool - { - $valid = $this->validateXrefs($this->getContent(), $this->getFullname(), $output); - foreach ($this->getMethods() as $method) { - $valid = $this->validateXrefs($method->getContent(), $method->getFullname(), $output) && $valid; - } - foreach ($this->getConstants() as $constant) { - $valid = $this->validateXrefs($constant->getContent(), $constant->getFullname(), $output) && $valid; - } - return $valid; - } - - /** - * Verifies that all {@see} tags are valid. - */ - private function validateXrefs(string $description, string $fullname, OutputInterface $output): bool - { - $valid = true; - preg_replace_callback( - '/ 1) { - $output->writeln(sprintf( - 'Xref not rendered propery in %s: %s', - $fullname, - $matches[1]) - ); - $valid = false; - return; // Invalid reference format - } - if (in_array( - $matches[1], - ['\\' . AppEngineFlexFormatter::class, '\\' .AppEngineFlexFormatterV2::class]) - ) { - return; // We cannot run "class_exists" on these because they will throw a fatal error. - } - if (class_exists($matches[1]) || interface_exists($matches[1] || trait_exists($matches[1]))) { - return; // Valid class reference - } - if (false !== strpos($matches[1], '::')) { - if (false !== strpos($matches[1], '()')) { - list($class, $method) = explode('::', str_replace('()', '', $matches[1])); - if (method_exists($class, $method)) { - return; // Valid method reference - } - if ('Async' === substr($method, -5)) { - return; // Skip magic Async methods - } - } elseif (defined($matches[1])) { - return; // Valid constant reference - } - } - - $output->writeln(sprintf('Invalid xref in %s: %s', $fullname, $matches[1])); - - if ($this->isProtobufMessageClass() || $this->isProtobufEnumClass()) { - // Don't report missing references for protobuf messages (we cant control these) - return; - } - $valid = false; - }, - $description - ); - - return $valid; - } }