From 34a5cb0d6cf1a6f7595bdbeeb30bcb56f202f3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 22 Jul 2021 09:10:53 +0200 Subject: [PATCH 1/2] Add support for generating classynopses from stubs --- build/gen_stub.php | 511 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 467 insertions(+), 44 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index efac19fe4233b..a0dc6a854ea7e 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -323,6 +323,22 @@ public function toArginfoType(): ArginfoType { return new ArginfoType($classTypes, $builtinTypes); } + public function getTypeForDoc(DOMDocument $doc): DOMElement { + if (count($this->types) > 1) { + $typeElement = $doc->createElement('type'); + $typeElement->setAttribute("class", "union"); + + foreach ($this->types as $type) { + $unionTypeElement = $doc->createElement('type', $type->name); + $typeElement->appendChild($unionTypeElement); + } + } else { + $typeElement = $doc->createElement('type', $this->types[0]->name); + } + + return $typeElement; + } + public static function equals(?Type $a, ?Type $b): bool { if ($a === null || $b === null) { return $a === $b; @@ -940,7 +956,7 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc $returnType = $this->return->getMethodSynopsisType(); if ($returnType) { - $this->appendMethodSynopsisTypeToElement($doc, $methodSynopsis, $returnType); + $methodSynopsis->appendChild($returnType->getTypeForDoc($doc)); } $methodname = $doc->createElement('methodname', $this->name->__toString()); @@ -962,7 +978,7 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc } $methodSynopsis->appendChild($methodparam); - $this->appendMethodSynopsisTypeToElement($doc, $methodparam, $arg->getMethodSynopsisType()); + $methodparam->appendChild($arg->getMethodSynopsisType()->getTypeForDoc($doc)); $parameter = $doc->createElement('parameter', $arg->name); if ($arg->sendBy !== ArgInfo::SEND_BY_VAL) { @@ -987,22 +1003,6 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc return $methodSynopsis; } - - private function appendMethodSynopsisTypeToElement(DOMDocument $doc, DOMElement $elementToAppend, Type $type) { - if (count($type->types) > 1) { - $typeElement = $doc->createElement('type'); - $typeElement->setAttribute("class", "union"); - - foreach ($type->types as $type) { - $unionTypeElement = $doc->createElement('type', $type->name); - $typeElement->appendChild($unionTypeElement); - } - } else { - $typeElement = $doc->createElement('type', $type->types[0]->name); - } - - $elementToAppend->appendChild($typeElement); - } } class PropertyInfo @@ -1015,13 +1015,19 @@ class PropertyInfo public $type; /** @var Expr|null */ public $defaultValue; + /** @var string|null */ + public $defaultValueString; + /** @var bool */ + public $isReadonly; - public function __construct(PropertyName $name, int $flags, ?Type $type, ?Expr $defaultValue) + public function __construct(PropertyName $name, int $flags, ?Type $type, ?Expr $defaultValue, ?string $defaultValueString, bool $isReadonly) { $this->name = $name; $this->flags = $flags; $this->type = $type; $this->defaultValue = $defaultValue; + $this->defaultValueString = $defaultValueString; + $this->isReadonly = $isReadonly; } public function discardInfoForOldPhpVersions(): void { @@ -1038,17 +1044,7 @@ public function getDeclaration(): string { $defaultValue = null; $defaultValueType = "undefined"; } else { - $evaluator = new ConstExprEvaluator( - function (Expr $expr) use (&$defaultValueConstant) { - if ($expr instanceof Expr\ConstFetch) { - $defaultValueConstant = true; - return null; - } - - throw new Exception("Property $this->name has an unsupported default value"); - } - ); - $defaultValue = $evaluator->evaluateDirectly($this->defaultValue); + $defaultValue = $this->evaluateDefaultValue($defaultValueConstant); $defaultValueType = gettype($defaultValue); } @@ -1184,6 +1180,68 @@ private function getFlagsAsString(): string return $flags; } + + public function getFieldSynopsisElement(DOMDocument $doc): DOMElement + { + $fieldsynopsisElement = $doc->createElement("fieldsynopsis"); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + + if ($this->flags & Class_::MODIFIER_PUBLIC) { + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "public")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } elseif ($this->flags & Class_::MODIFIER_PROTECTED) { + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "protected")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "private")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } + + if ($this->flags & Class_::MODIFIER_STATIC) { + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "static")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } elseif ($this->flags & Class_::MODIFIER_READONLY || $this->isReadonly) { + $fieldsynopsisElement->appendChild($doc->createElement("modifier", "readonly")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } + + if ($this->type) { + $fieldsynopsisElement->appendChild($this->type->getTypeForDoc($doc)); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } + + $className = str_replace("\\", "-", $this->name->class->toLowerString()); + $varnameElement = $doc->createElement("varname", $this->name->property); + $varnameElement->setAttribute("linkend", "$className.props." . strtolower($this->name->property)); + $fieldsynopsisElement->appendChild($varnameElement); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + + if ($this->defaultValueString) { + $fieldsynopsisElement->appendChild(new DOMText(" ")); + $initializerElement = $doc->createElement("initializer", $this->defaultValueString); + $fieldsynopsisElement->appendChild($initializerElement); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + } + + return $fieldsynopsisElement; + } + + /** @return mixed */ + private function evaluateDefaultValue(bool &$defaultValueConstant) + { + $evaluator = new ConstExprEvaluator( + function (Expr $expr) use (&$defaultValueConstant) { + if ($expr instanceof Expr\ConstFetch) { + $defaultValueConstant = true; + return null; + } + + throw new Exception("Property $this->name has an unsupported default value"); + } + ); + + return $evaluator->evaluateDirectly($this->defaultValue); + } } class ClassInfo { @@ -1332,6 +1390,285 @@ private function getFlagsAsString(): string return implode("|", $flags); } + + /** + * @param ClassInfo[] $classMap + */ + public function getClassSynopsisDocument(array $classMap): ?string { + + $doc = new DOMDocument(); + $doc->formatOutput = true; + $classSynopsis = $this->getClassSynopsisElement($doc, $classMap); + if (!$classSynopsis) { + return null; + } + + $doc->appendChild($classSynopsis); + + return $doc->saveXML(); + } + + /** + * @param ClassInfo[] $classMap + */ + public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOMElement { + + $classSynopsis = $doc->createElement("classsynopsis"); + $classSynopsis->appendChild(new DOMText("\n ")); + + $ooElement = self::createOoElement($doc, $this, false, false); + if (!$ooElement) { + return null; + } + $classSynopsis->appendChild($ooElement); + $classSynopsis->appendChild(new DOMText("\n\n ")); + + $classSynopsisInfo = $doc->createElement("classsynopsisinfo"); + $classSynopsisInfo->appendChild(new DOMText("\n ")); + $ooElement = self::createOoElement($doc, $this, true, false); + if (!$ooElement) { + return null; + } + $classSynopsisInfo->appendChild($ooElement); + $classSynopsisInfo->appendChild(new DOMText("\n ")); + + $classSynopsis->appendChild($classSynopsisInfo); + $classSynopsis->appendChild(new DOMText("\n ")); + + foreach ($this->extends as $k => $parent) { + $parentInfo = $classMap[$parent->toString()] ?? null; + if (!$parentInfo) { + throw new Exception("Missing parent class " . $parent->toString()); + } + + $ooElement = self::createOoElement($doc, $parentInfo, false, $k === 0 && $parentInfo->type === "class"); + if (!$ooElement) { + return null; + } + + $classSynopsisInfo->appendChild(new DOMText("\n ")); + $classSynopsisInfo->appendChild($ooElement); + $classSynopsisInfo->appendChild(new DOMText("\n ")); + } + + foreach ($this->implements as $interface) { + $interfaceInfo = $classMap[$interface->toString()] ?? null; + if (!$interfaceInfo) { + throw new Exception("Missing implemented interface " . $interface->toString()); + } + + $ooElement = self::createOoElement($doc, $interfaceInfo, false, false); + if (!$ooElement) { + return null; + } + $classSynopsisInfo->appendChild(new DOMText("\n ")); + $classSynopsisInfo->appendChild($ooElement); + $classSynopsisInfo->appendChild(new DOMText("\n ")); + } + + /** @var Name[] $parentsWithInheritedProperties */ + $parentsWithInheritedProperties = []; + /** @var Name[] $parentsWithInheritedMethods */ + $parentsWithInheritedMethods = []; + + $this->collectInheritedMembers($parentsWithInheritedProperties, $parentsWithInheritedMethods, $classMap); + + if (!empty($this->propertyInfos)) { + $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + $classSynopsis->appendChild(new DOMText("\n ")); + + foreach ($this->propertyInfos as $propertyInfo) { + $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc); + $classSynopsis->appendChild($fieldSynopsisElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + } + + if (!empty($parentsWithInheritedProperties)) { + $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedProperties;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + $classSynopsis->appendChild(new DOMText("\n ")); + + foreach ($parentsWithInheritedProperties as $parent) { + $parentClassName = self::getClassSynopsisFilename($parent); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$parentClassName')/db:partintro/db:section/db:classsynopsis/db:fieldsynopsis[preceding-sibling::db:classsynopsisinfo[1][@role='comment' and text()='&Properties;']]))" + ); + $classSynopsis->appendChild($includeElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + } + + if (!empty($this->funcInfos)) { + $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Methods;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + $classSynopsis->appendChild(new DOMText("\n ")); + + $className = self::getClassSynopsisFilename($this->name); + + if ($this->hasConstructor()) { + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[not(@role='procedural')]" + ); + $classSynopsis->appendChild($includeElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + + if ($this->hasMethods()) { + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[1])" + ); + $classSynopsis->appendChild($includeElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + + if ($this->hasDestructor()) { + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[not(@role='procedural')]" + ); + $classSynopsis->appendChild($includeElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + } + + if (!empty($parentsWithInheritedMethods)) { + $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedMethods;"); + $classSynopsisInfo->setAttribute("role", "comment"); + $classSynopsis->appendChild($classSynopsisInfo); + $classSynopsis->appendChild(new DOMText("\n ")); + + foreach ($parentsWithInheritedMethods as $parent) { + $parentClassName = self::getClassSynopsisFilename($parent); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$parentClassName')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[1])" + ); + $classSynopsis->appendChild($includeElement); + $classSynopsis->appendChild(new DOMText("\n ")); + } + } + + return $classSynopsis; + } + + private static function createOoElement(DOMDocument $doc, ClassInfo $classInfo, bool $withModifiers, bool $isExtends): ?DOMElement { + if ($classInfo->type !== "class" && $classInfo->type !== "interface") { + echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n"; + return null; + } + + $ooElement = $doc->createElement('oo' . $classInfo->type); + $ooElement->appendChild(new DOMText("\n ")); + if ($isExtends) { + $ooElement->appendChild($doc->createElement('modifier', 'extends')); + $ooElement->appendChild(new DOMText("\n ")); + } elseif ($withModifiers) { + if ($classInfo->flags & Class_::MODIFIER_FINAL) { + $ooElement->appendChild($doc->createElement('modifier', 'final')); + $ooElement->appendChild(new DOMText("\n ")); + } + if ($classInfo->flags & Class_::MODIFIER_ABSTRACT) { + $ooElement->appendChild($doc->createElement('modifier', 'abstract')); + $ooElement->appendChild(new DOMText("\n ")); + } + } + + $nameElement = $doc->createElement($classInfo->type . 'name', $classInfo->name->toString()); + $ooElement->appendChild($nameElement); + $ooElement->appendChild(new DOMText("\n ")); + + return $ooElement; + } + + public static function getClassSynopsisFilename(Name $name): string { + return strtolower(implode('-', $name->parts)); + } + + /** + * @param Name[] $parentsWithInheritedProperties + * @param Name[] $parentsWithInheritedMethods + * @param ClassInfo[] $classMap + */ + private function collectInheritedMembers(array &$parentsWithInheritedProperties, array &$parentsWithInheritedMethods, array $classMap): void + { + if ($this->type !== "class") { + return; + } + + foreach ($this->extends as $parent) { + $parentInfo = $classMap[$parent->toString()] ?? null; + if (!$parentInfo) { + throw new Exception("Missing parent class " . $parent->toString()); + } + + if (!empty($parentInfo->propertyInfos) && !isset($parentsWithInheritedProperties[$parent->toString()])) { + $parentsWithInheritedProperties[$parent->toString()] = $parent; + } + + if (!empty($parentInfo->funcInfos) && !isset($parentsWithInheritedMethods[$parent->toString()])) { + $parentsWithInheritedMethods[$parent->toString()] = $parent; + } + + $parentInfo->collectInheritedMembers($parentsWithInheritedProperties, $parentsWithInheritedMethods, $classMap); + } + } + + private function hasConstructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isConstructor()) { + return true; + } + } + + return false; + } + + private function hasDestructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isDestructor()) { + return true; + } + } + + return false; + } + + private function hasMethods(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if (!$funcInfo->name->isConstructor() && !$funcInfo->name->isDestructor()) { + return true; + } + } + + return false; + } + + private function createIncludeElement(DOMDocument $doc, string $query): DOMElement + { + $includeElement = $doc->createElement("xi:include"); + $includeElement->setAttribute("xpointer", $query); + $fallbackElement = $doc->createElement("xi:fallback"); + $includeElement->appendChild(new DOMText("\n ")); + $includeElement->appendChild($fallbackElement); + $includeElement->appendChild(new DOMText("\n ")); + + return $includeElement; + } } class FileInfo { @@ -1590,15 +1927,19 @@ function parseProperty( int $flags, Stmt\PropertyProperty $property, ?Node $type, - ?DocComment $comment + ?DocComment $comment, + PrettyPrinterAbstract $prettyPrinter ): PropertyInfo { $docType = false; + $isReadonly = false; if ($comment) { $tags = parseDocComment($comment); foreach ($tags as $tag) { if ($tag->name === 'var') { $docType = true; + } elseif ($tag->name === 'readonly') { + $isReadonly = true; } } } @@ -1623,7 +1964,9 @@ function parseProperty( new PropertyName($class, $property->name->__toString()), $flags, $propertyType, - $property->default + $property->default, + $property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null, + $isReadonly ); } @@ -1786,7 +2129,8 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac $flags, $property, $classStmt->type, - $classStmt->getDocComment() + $classStmt->getDocComment(), + $prettyPrinter ); } } else if ($classStmt instanceof Stmt\ClassMethod) { @@ -2069,6 +2413,33 @@ function generateFunctionEntries(?Name $className, array $funcInfos): string { return $code; } +/** + * @param ClassInfo[] $classMap + * @return array + */ +function generateClassSynopses(array $classMap): array { + $result = []; + + foreach ($classMap as $classInfo) { + $classSynopsis = $classInfo->getClassSynopsisDocument($classMap); + if ($classSynopsis !== null) { + $result[ClassInfo::getClassSynopsisFilename($classInfo->name) . ".xml"] = $classSynopsis; + } + } + + return $result; +} + + +/** + * @param ClassInfo[] $classMap + * @return array + */ +function replaceClassSynopses(string $targetDirectory, array $classMap): array +{ + throw new Exception("Not yet implemented!"); +} + /** * @param FuncInfo[] $funcMap * @param FuncInfo[] $aliasMap @@ -2322,22 +2693,37 @@ function initPhpParser() { } $optind = null; -$options = getopt("fh", ["force-regeneration", "parameter-stats", "help", "verify", "generate-methodsynopses", "replace-methodsynopses"], $optind); +$options = getopt( + "fh", + [ + "force-regeneration", "parameter-stats", "help", "verify", "generate-classsynopses", "replace-classsynopses", + "generate-methodsynopses", "replace-methodsynopses" + ], + $optind +); $context = new Context; $printParameterStats = isset($options["parameter-stats"]); $verify = isset($options["verify"]); +$generateClassSynopses = isset($options["generate-classsynopses"]); +$replaceClassSynopses = isset($options["replace-classsynopses"]); $generateMethodSynopses = isset($options["generate-methodsynopses"]); $replaceMethodSynopses = isset($options["replace-methodsynopses"]); $context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]); -$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateMethodSynopses || $replaceMethodSynopses; -$targetMethodSynopses = $argv[$optind + 1] ?? null; +$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateClassSynopses || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses; + +$targetClassSynopses = $argv[$optind + 1] ?? null; +if ($replaceClassSynopses && $targetClassSynopses === null) { + die("A target class synopsis directory must be provided for.\n"); +} + +$targetMethodSynopses = $argv[$optind + 1 + ($targetClassSynopses !== null)] ?? null; if ($replaceMethodSynopses && $targetMethodSynopses === null) { - die("A target directory must be provided.\n"); + die("A target method synopsis directory must be provided.\n"); } if (isset($options["h"]) || isset($options["help"])) { - die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n"); + die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --generate-classsynopses ] [ --replace-classsynopses ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n"); } $fileInfos = []; @@ -2375,19 +2761,28 @@ function initPhpParser() { echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n"; } +/** @var ClassInfo[] $classMap */ +$classMap = []; /** @var FuncInfo[] $funcMap */ $funcMap = []; /** @var FuncInfo[] $aliasMap */ $aliasMap = []; foreach ($fileInfos as $fileInfo) { - foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { - /** @var FuncInfo $funcInfo */ - $funcMap[$funcInfo->name->__toString()] = $funcInfo; + foreach ($fileInfo->classInfos as $classInfo) { + $classMap[$classInfo->name->__toString()] = $classInfo; - // TODO: Don't use aliasMap for methodsynopsis? - if ($funcInfo->aliasType === "alias") { - $aliasMap[$funcInfo->alias->__toString()] = $funcInfo; + foreach ($fileInfo->funcInfos as $funcInfo) { + $funcMap[$funcInfo->name->__toString()] = $funcInfo; + } + + foreach ($classInfo->funcInfos as $funcInfo) { + $funcMap[$funcInfo->name->__toString()] = $funcInfo; + + // TODO: Don't use aliasMap for methodsynopsis? + if ($funcInfo->aliasType === "alias") { + $aliasMap[$funcInfo->alias->__toString()] = $funcInfo; + } } } } @@ -2468,6 +2863,34 @@ function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc } } +if ($generateClassSynopses) { + $classSynopsesDirectory = getcwd() . "/classsynopses"; + + $classSynopses = generateClassSynopses($classMap); + if (!empty($classSynopses)) { + if (!file_exists($classSynopsesDirectory)) { + mkdir($classSynopsesDirectory); + } + + foreach ($classSynopses as $filename => $content) { + if (file_put_contents("$classSynopsesDirectory/$filename", $content)) { + echo "Saved $filename\n"; + } + } + } +} + +if ($replaceClassSynopses) { + $classSynopses = replaceClassSynopses($targetClassSynopses, $classMap); + + foreach ($classSynopses as $filename => $content) { + if (file_put_contents($filename, $content)) { + echo "Saved $filename\n"; + } + } +} + + if ($generateMethodSynopses) { $methodSynopsesDirectory = getcwd() . "/methodsynopses"; From 05b61df65a34bac4aafd69547f7e0668444eac72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 29 Jul 2021 11:00:25 +0200 Subject: [PATCH 2/2] Fix whitespaces --- build/gen_stub.php | 93 ++++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index a0dc6a854ea7e..1e3f09990c84e 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1184,45 +1184,45 @@ private function getFlagsAsString(): string public function getFieldSynopsisElement(DOMDocument $doc): DOMElement { $fieldsynopsisElement = $doc->createElement("fieldsynopsis"); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); if ($this->flags & Class_::MODIFIER_PUBLIC) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($doc->createElement("modifier", "public")); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } elseif ($this->flags & Class_::MODIFIER_PROTECTED) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($doc->createElement("modifier", "protected")); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } elseif ($this->flags & Class_::MODIFIER_PRIVATE) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($doc->createElement("modifier", "private")); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } if ($this->flags & Class_::MODIFIER_STATIC) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($doc->createElement("modifier", "static")); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } elseif ($this->flags & Class_::MODIFIER_READONLY || $this->isReadonly) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($doc->createElement("modifier", "readonly")); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } if ($this->type) { + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($this->type->getTypeForDoc($doc)); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } $className = str_replace("\\", "-", $this->name->class->toLowerString()); $varnameElement = $doc->createElement("varname", $this->name->property); $varnameElement->setAttribute("linkend", "$className.props." . strtolower($this->name->property)); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $fieldsynopsisElement->appendChild($varnameElement); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); if ($this->defaultValueString) { - $fieldsynopsisElement->appendChild(new DOMText(" ")); + $fieldsynopsisElement->appendChild(new DOMText("\n ")); $initializerElement = $doc->createElement("initializer", $this->defaultValueString); $fieldsynopsisElement->appendChild($initializerElement); - $fieldsynopsisElement->appendChild(new DOMText("\n ")); } + $fieldsynopsisElement->appendChild(new DOMText("\n ")); + return $fieldsynopsisElement; } @@ -1414,26 +1414,24 @@ public function getClassSynopsisDocument(array $classMap): ?string { public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOMElement { $classSynopsis = $doc->createElement("classsynopsis"); - $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild(new DOMText("\n ")); - $ooElement = self::createOoElement($doc, $this, false, false); + $ooElement = self::createOoElement($doc, $this, false, false, 4); if (!$ooElement) { return null; } $classSynopsis->appendChild($ooElement); - $classSynopsis->appendChild(new DOMText("\n\n ")); + $classSynopsis->appendChild(new DOMText("\n\n ")); $classSynopsisInfo = $doc->createElement("classsynopsisinfo"); - $classSynopsisInfo->appendChild(new DOMText("\n ")); - $ooElement = self::createOoElement($doc, $this, true, false); + $classSynopsisInfo->appendChild(new DOMText("\n ")); + $ooElement = self::createOoElement($doc, $this, true, false, 5); if (!$ooElement) { return null; } $classSynopsisInfo->appendChild($ooElement); - $classSynopsisInfo->appendChild(new DOMText("\n ")); $classSynopsis->appendChild($classSynopsisInfo); - $classSynopsis->appendChild(new DOMText("\n ")); foreach ($this->extends as $k => $parent) { $parentInfo = $classMap[$parent->toString()] ?? null; @@ -1441,14 +1439,13 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOM throw new Exception("Missing parent class " . $parent->toString()); } - $ooElement = self::createOoElement($doc, $parentInfo, false, $k === 0 && $parentInfo->type === "class"); + $ooElement = self::createOoElement($doc, $parentInfo, false, $k === 0 && $parentInfo->type === "class", 5); if (!$ooElement) { return null; } - $classSynopsisInfo->appendChild(new DOMText("\n ")); + $classSynopsisInfo->appendChild(new DOMText("\n\n ")); $classSynopsisInfo->appendChild($ooElement); - $classSynopsisInfo->appendChild(new DOMText("\n ")); } foreach ($this->implements as $interface) { @@ -1457,14 +1454,14 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOM throw new Exception("Missing implemented interface " . $interface->toString()); } - $ooElement = self::createOoElement($doc, $interfaceInfo, false, false); + $ooElement = self::createOoElement($doc, $interfaceInfo, false, false, 5); if (!$ooElement) { return null; } - $classSynopsisInfo->appendChild(new DOMText("\n ")); + $classSynopsisInfo->appendChild(new DOMText("\n\n ")); $classSynopsisInfo->appendChild($ooElement); - $classSynopsisInfo->appendChild(new DOMText("\n ")); } + $classSynopsisInfo->appendChild(new DOMText("\n ")); /** @var Name[] $parentsWithInheritedProperties */ $parentsWithInheritedProperties = []; @@ -1474,120 +1471,126 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOM $this->collectInheritedMembers($parentsWithInheritedProperties, $parentsWithInheritedMethods, $classMap); if (!empty($this->propertyInfos)) { - $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild(new DOMText("\n\n ")); $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;"); $classSynopsisInfo->setAttribute("role", "comment"); $classSynopsis->appendChild($classSynopsisInfo); - $classSynopsis->appendChild(new DOMText("\n ")); foreach ($this->propertyInfos as $propertyInfo) { + $classSynopsis->appendChild(new DOMText("\n ")); $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc); $classSynopsis->appendChild($fieldSynopsisElement); - $classSynopsis->appendChild(new DOMText("\n ")); } } if (!empty($parentsWithInheritedProperties)) { - $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild(new DOMText("\n\n ")); $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedProperties;"); $classSynopsisInfo->setAttribute("role", "comment"); $classSynopsis->appendChild($classSynopsisInfo); - $classSynopsis->appendChild(new DOMText("\n ")); foreach ($parentsWithInheritedProperties as $parent) { + $classSynopsis->appendChild(new DOMText("\n ")); $parentClassName = self::getClassSynopsisFilename($parent); $includeElement = $this->createIncludeElement( $doc, "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$parentClassName')/db:partintro/db:section/db:classsynopsis/db:fieldsynopsis[preceding-sibling::db:classsynopsisinfo[1][@role='comment' and text()='&Properties;']]))" ); $classSynopsis->appendChild($includeElement); - $classSynopsis->appendChild(new DOMText("\n ")); } } if (!empty($this->funcInfos)) { - $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild(new DOMText("\n\n ")); $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Methods;"); $classSynopsisInfo->setAttribute("role", "comment"); $classSynopsis->appendChild($classSynopsisInfo); - $classSynopsis->appendChild(new DOMText("\n ")); $className = self::getClassSynopsisFilename($this->name); if ($this->hasConstructor()) { + $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[not(@role='procedural')]" ); $classSynopsis->appendChild($includeElement); - $classSynopsis->appendChild(new DOMText("\n ")); } if ($this->hasMethods()) { + $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[1])" ); $classSynopsis->appendChild($includeElement); - $classSynopsis->appendChild(new DOMText("\n ")); } if ($this->hasDestructor()) { + $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$className')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[not(@role='procedural')]" ); $classSynopsis->appendChild($includeElement); - $classSynopsis->appendChild(new DOMText("\n ")); } } if (!empty($parentsWithInheritedMethods)) { - $classSynopsis->appendChild(new DOMText("\n ")); + $classSynopsis->appendChild(new DOMText("\n\n ")); $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedMethods;"); $classSynopsisInfo->setAttribute("role", "comment"); $classSynopsis->appendChild($classSynopsisInfo); - $classSynopsis->appendChild(new DOMText("\n ")); foreach ($parentsWithInheritedMethods as $parent) { + $classSynopsis->appendChild(new DOMText("\n ")); $parentClassName = self::getClassSynopsisFilename($parent); $includeElement = $this->createIncludeElement( $doc, "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.$parentClassName')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[1])" ); $classSynopsis->appendChild($includeElement); - $classSynopsis->appendChild(new DOMText("\n ")); } } + $classSynopsis->appendChild(new DOMText("\n ")); + return $classSynopsis; } - private static function createOoElement(DOMDocument $doc, ClassInfo $classInfo, bool $withModifiers, bool $isExtends): ?DOMElement { + private static function createOoElement( + DOMDocument $doc, + ClassInfo $classInfo, + bool $withModifiers, + bool $isExtends, + int $indentationLevel + ): ?DOMElement { + $indentation = str_repeat(" ", $indentationLevel); + if ($classInfo->type !== "class" && $classInfo->type !== "interface") { echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n"; return null; } $ooElement = $doc->createElement('oo' . $classInfo->type); - $ooElement->appendChild(new DOMText("\n ")); + $ooElement->appendChild(new DOMText("\n$indentation ")); if ($isExtends) { $ooElement->appendChild($doc->createElement('modifier', 'extends')); - $ooElement->appendChild(new DOMText("\n ")); + $ooElement->appendChild(new DOMText("\n$indentation ")); } elseif ($withModifiers) { if ($classInfo->flags & Class_::MODIFIER_FINAL) { $ooElement->appendChild($doc->createElement('modifier', 'final')); - $ooElement->appendChild(new DOMText("\n ")); + $ooElement->appendChild(new DOMText("\n$indentation ")); } if ($classInfo->flags & Class_::MODIFIER_ABSTRACT) { $ooElement->appendChild($doc->createElement('modifier', 'abstract')); - $ooElement->appendChild(new DOMText("\n ")); + $ooElement->appendChild(new DOMText("\n$indentation ")); } } $nameElement = $doc->createElement($classInfo->type . 'name', $classInfo->name->toString()); $ooElement->appendChild($nameElement); - $ooElement->appendChild(new DOMText("\n ")); + $ooElement->appendChild(new DOMText("\n$indentation")); return $ooElement; } @@ -1663,9 +1666,9 @@ private function createIncludeElement(DOMDocument $doc, string $query): DOMEleme $includeElement = $doc->createElement("xi:include"); $includeElement->setAttribute("xpointer", $query); $fallbackElement = $doc->createElement("xi:fallback"); - $includeElement->appendChild(new DOMText("\n ")); + $includeElement->appendChild(new DOMText("\n ")); $includeElement->appendChild($fallbackElement); - $includeElement->appendChild(new DOMText("\n ")); + $includeElement->appendChild(new DOMText("\n ")); return $includeElement; }