1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

[skip ci] gen_stub: Generate useful methodsynopsises for new functions/methods (#12330)

Co-authored-by: Máté Kocsis <kocsismate@woohoolabs.com>
This commit is contained in:
Gina Peter Banyard
2023-10-31 19:45:34 +00:00
committed by GitHub
parent 2782577c4f
commit 5d98069456

View File

@@ -1060,7 +1060,7 @@ class FunctionName implements FunctionOrMethodName {
}
public function getMethodSynopsisFilename(): string {
return implode('_', $this->name->getParts());
return 'functions/' . implode('/', str_replace('_', '-', $this->name->getParts()));
}
public function getNameForAttributes(): string {
@@ -1105,8 +1105,11 @@ class MethodName implements FunctionOrMethodName {
return "arginfo_class_{$this->getDeclarationClassName()}_{$this->methodName}";
}
public function getMethodSynopsisFilename(): string {
return $this->getDeclarationClassName() . "_{$this->methodName}";
public function getMethodSynopsisFilename(): string
{
$parts = [...$this->className->getParts(), ltrim($this->methodName, '_')];
/* File paths are in lowercase */
return strtolower(implode('/', $parts));
}
public function getNameForAttributes(): string {
@@ -1480,25 +1483,447 @@ class FuncInfo {
return $flags;
}
private function generateRefSect1(DOMDocument $doc, string $role): DOMElement {
$refSec = $doc->createElement('refsect1');
$refSec->setAttribute('role', $role);
$refSec->append(
"\n ",
$doc->createEntityReference('reftitle.' . $role),
"\n "
);
return $refSec;
}
/**
* @param array<string, FuncInfo> $funcMap
* @param array<string, FuncInfo> $aliasMap
* @throws Exception
*/
public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string {
$REFSEC1_SEPERATOR = "\n\n ";
$doc = new DOMDocument();
$doc = new DOMDocument("1.0", "utf-8");
$doc->formatOutput = true;
$refentry = $doc->createElement('refentry');
$doc->appendChild($refentry);
if ($this->isMethod()) {
assert($this->name instanceof MethodName);
/* Namespaces are seperated by '-', '_' must be converted to '-' too.
* Trim away the __ for magic methods */
$id = strtolower(
str_replace('\\', '-', $this->name->className->__toString())
. '.'
. str_replace('_', '-', ltrim($this->name->methodName, '_'))
);
} else {
$id = 'function.' . strtolower(str_replace('_', '-', $this->name->__toString()));
}
$refentry->setAttribute("xml:id", $id);
/* We create an attribute for xmlns, as libxml otherwise force it to be the first one */
//$refentry->setAttribute("xmlns", "http://docbook.org/ns/docbook");
$namespace = $doc->createAttribute('xmlns');
$namespace->value = "http://docbook.org/ns/docbook";
$refentry->setAttributeNode($namespace);
$refentry->setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
$refentry->appendChild(new DOMText("\n "));
/* Creation of <refnamediv> */
$refnamediv = $doc->createElement('refnamediv');
$refnamediv->appendChild(new DOMText("\n "));
$refname = $doc->createElement('refname', $this->name->__toString());
$refnamediv->appendChild($refname);
$refnamediv->appendChild(new DOMText("\n "));
$refpurpose = $doc->createElement('refpurpose', 'Description');
$refnamediv->appendChild($refpurpose);
$refnamediv->appendChild(new DOMText("\n "));
$refentry->append($refnamediv, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="description"> */
$descriptionRefSec = $this->generateRefSect1($doc, 'description');
$methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc);
if (!$methodSynopsis) {
return null;
}
$descriptionRefSec->appendChild($methodSynopsis);
$descriptionRefSec->appendChild(new DOMText("\n "));
$undocumentedEntity = $doc->createEntityReference('warn.undocumented.func');
$descriptionRefSec->appendChild($undocumentedEntity);
$descriptionRefSec->appendChild(new DOMText("\n "));
$returnDescriptionPara = $doc->createElement('para');
$returnDescriptionPara->appendChild(new DOMText("\n Description.\n "));
$descriptionRefSec->appendChild($returnDescriptionPara);
$doc->appendChild($methodSynopsis);
$descriptionRefSec->appendChild(new DOMText("\n "));
$refentry->append($descriptionRefSec, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="parameters"> */
$parametersRefSec = $this->getParameterSection($doc);
$refentry->append($parametersRefSec, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="returnvalues"> */
if (!$this->name->isConstructor() && !$this->name->isDestructor()) {
$returnRefSec = $this->getReturnValueSection($doc);
$refentry->append($returnRefSec, $REFSEC1_SEPERATOR);
}
/* Creation of <refsect1 role="errors"> */
$errorsRefSec = $this->generateRefSect1($doc, 'errors');
$errorsDescriptionParaConstantTag = $doc->createElement('constant');
$errorsDescriptionParaConstantTag->append('E_*');
$errorsDescriptionParaExceptionTag = $doc->createElement('exceptionname');
$errorsDescriptionParaExceptionTag->append('Exception');
$errorsDescriptionPara = $doc->createElement('para');
$errorsDescriptionPara->append(
"\n When does this function issue ",
$errorsDescriptionParaConstantTag,
" level errors,\n and/or throw ",
$errorsDescriptionParaExceptionTag,
"s.\n "
);
$errorsRefSec->appendChild($errorsDescriptionPara);
$errorsRefSec->appendChild(new DOMText("\n "));
$refentry->append($errorsRefSec, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="changelog"> */
$changelogRefSec = $this->getChangelogSection($doc);
$refentry->append($changelogRefSec, $REFSEC1_SEPERATOR);
$exampleRefSec = $this->getExampleSection($doc, $id);
$refentry->append($exampleRefSec, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="notes"> */
$notesRefSec = $this->generateRefSect1($doc, 'notes');
$noteTagSimara = $doc->createElement('simpara');
$noteTagSimara->append(
"\n Any notes that don't fit anywhere else should go here.\n "
);
$noteTag = $doc->createElement('note');
$noteTag->append("\n ", $noteTagSimara, "\n ");
$notesRefSec->append($noteTag, "\n ");
$refentry->append($notesRefSec, $REFSEC1_SEPERATOR);
/* Creation of <refsect1 role="seealso"> */
$seeAlsoRefSec = $this->generateRefSect1($doc, 'seealso');
$seeAlsoMemberClassMethod = $doc->createElement('member');
$seeAlsoMemberClassMethodTag = $doc->createElement('methodname');
$seeAlsoMemberClassMethodTag->appendChild(new DOMText("ClassName::otherMethodName"));
$seeAlsoMemberClassMethod->appendChild($seeAlsoMemberClassMethodTag);
$seeAlsoMemberFunction = $doc->createElement('member');
$seeAlsoMemberFunctionTag = $doc->createElement('function');
$seeAlsoMemberFunctionTag->appendChild(new DOMText("some_function"));
$seeAlsoMemberFunction->appendChild($seeAlsoMemberFunctionTag);
$seeAlsoMemberLink = $doc->createElement('member');
$seeAlsoMemberLinkTag = $doc->createElement('link');
$seeAlsoMemberLinkTag->setAttribute('linkend', 'some.id.chunk.to.link');
$seeAlsoMemberLinkTag->appendChild(new DOMText('something appendix'));
$seeAlsoMemberLink->appendChild($seeAlsoMemberLinkTag);
$seeAlsoList = $doc->createElement('simplelist');
$seeAlsoList->append(
"\n ",
$seeAlsoMemberClassMethod,
"\n ",
$seeAlsoMemberFunction,
"\n ",
$seeAlsoMemberLink,
"\n "
);
$seeAlsoRefSec->appendChild($seeAlsoList);
$seeAlsoRefSec->appendChild(new DOMText("\n "));
$refentry->appendChild($seeAlsoRefSec);
$refentry->appendChild(new DOMText("\n\n"));
$doc->appendChild(new DOMComment(
<<<ENDCOMMENT
Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
ENDCOMMENT
));
return $doc->saveXML();
}
private function getParameterSection(DOMDocument $doc): DOMElement {
$parametersRefSec = $this->generateRefSect1($doc, 'parameters');
if (empty($this->args)) {
$noParamEntity = $doc->createEntityReference('no.function.parameters');
$parametersRefSec->appendChild($noParamEntity);
return $parametersRefSec;
} else {
$parametersPara = $doc->createElement('para');
$parametersRefSec->appendChild($parametersPara);
$parametersPara->appendChild(new DOMText("\n "));
$parametersList = $doc->createElement('variablelist');
$parametersPara->appendChild($parametersList);
/*
<varlistentry>
<term><parameter>name</parameter></term>
<listitem>
<para>
Description.
</para>
</listitem>
</varlistentry>
*/
foreach ($this->args as $arg) {
$parameter = $doc->createElement('parameter', $arg->name);
$parameterTerm = $doc->createElement('term');
$parameterTerm->appendChild($parameter);
$listItemPara = $doc->createElement('para');
$listItemPara->append(
"\n ",
"Description.",
"\n ",
);
$parameterEntryListItem = $doc->createElement('listitem');
$parameterEntryListItem->append(
"\n ",
$listItemPara,
"\n ",
);
$parameterEntry = $doc->createElement('varlistentry');
$parameterEntry->append(
"\n ",
$parameterTerm,
"\n ",
$parameterEntryListItem,
"\n ",
);
$parametersList->appendChild(new DOMText("\n "));
$parametersList->appendChild($parameterEntry);
}
$parametersList->appendChild(new DOMText("\n "));
}
$parametersPara->appendChild(new DOMText("\n "));
$parametersRefSec->appendChild(new DOMText("\n "));
return $parametersRefSec;
}
private function getReturnValueSection(DOMDocument $doc): DOMElement {
$returnRefSec = $this->generateRefSect1($doc, 'returnvalues');
$returnDescriptionPara = $doc->createElement('para');
$returnDescriptionPara->appendChild(new DOMText("\n "));
$returnType = $this->return->getMethodSynopsisType();
if ($returnType === null) {
$returnDescriptionPara->appendChild(new DOMText("Description."));
} else if (count($returnType->types) === 1) {
$type = $returnType->types[0];
$name = $type->name;
switch ($name) {
case 'void':
$descriptionNode = $doc->createEntityReference('return.void');
break;
case 'true':
$descriptionNode = $doc->createEntityReference('return.true.always');
break;
case 'bool':
$descriptionNode = $doc->createEntityReference('return.success');
break;
default:
$descriptionNode = new DOMText("Description.");
break;
}
$returnDescriptionPara->appendChild($descriptionNode);
} else {
$returnDescriptionPara->appendChild(new DOMText("Description."));
}
$returnDescriptionPara->appendChild(new DOMText("\n "));
$returnRefSec->appendChild($returnDescriptionPara);
$returnRefSec->appendChild(new DOMText("\n "));
return $returnRefSec;
}
/**
* @param array<DOMNode> $headers [count($headers) === $columns]
* @param array<array<DOMNode>> $rows [count($rows[$i]) === $columns]
*/
private function generateDocbookInformalTable(
DOMDocument $doc,
int $indent,
int $columns,
array $headers,
array $rows
): DOMElement {
$strIndent = str_repeat(' ', $indent);
$headerRow = $doc->createElement('row');
foreach ($headers as $header) {
$headerEntry = $doc->createElement('entry');
$headerEntry->appendChild($header);
$headerRow->append("\n$strIndent ", $headerEntry);
}
$headerRow->append("\n$strIndent ");
$thead = $doc->createElement('thead');
$thead->append(
"\n$strIndent ",
$headerRow,
"\n$strIndent ",
);
$tbody = $doc->createElement('tbody');
foreach ($rows as $row) {
$bodyRow = $doc->createElement('row');
foreach ($row as $cell) {
$entry = $doc->createElement('entry');
$entry->appendChild($cell);
$bodyRow->appendChild(new DOMText("\n$strIndent "));
$bodyRow->appendChild($entry);
}
$bodyRow->appendChild(new DOMText("\n$strIndent "));
$tbody->append(
"\n$strIndent ",
$bodyRow,
"\n$strIndent ",
);
}
$tgroup = $doc->createElement('tgroup');
$tgroup->setAttribute('cols', (string) $columns);
$tgroup->append(
"\n$strIndent ",
$thead,
"\n$strIndent ",
$tbody,
"\n$strIndent ",
);
$table = $doc->createElement('informaltable');
$table->append(
"\n$strIndent ",
$tgroup,
"\n$strIndent",
);
return $table;
}
private function getChangelogSection(DOMDocument $doc): DOMElement {
$refSec = $this->generateRefSect1($doc, 'changelog');
$headers = [
$doc->createEntityReference('Version'),
$doc->createEntityReference('Description'),
];
$rows = [[
new DOMText('8.X.0'),
new DOMText("\n Description\n "),
]];
$table = $this->generateDocbookInformalTable(
$doc,
/* indent: */ 2,
/* columns: */ 2,
/* headers: */ $headers,
/* rows: */ $rows
);
$refSec->appendChild($table);
$refSec->appendChild(new DOMText("\n "));
return $refSec;
}
private function getExampleSection(DOMDocument $doc, string $id): DOMElement {
$refSec = $this->generateRefSect1($doc, 'examples');
$example = $doc->createElement('example');
$fnName = $this->name->__toString();
$example->setAttribute('xml:id', $id . '.example.basic');
$title = $doc->createElement('title');
$fn = $doc->createElement($this->isMethod() ? 'methodname' : 'function');
$fn->append($fnName);
$title->append($fn, ' example');
$example->append("\n ", $title);
$para = $doc->createElement('para');
$para->append("\n ", "Description.", "\n ");
$example->append("\n ", $para);
$prog = $doc->createElement('programlisting');
$prog->setAttribute('role', 'php');
$code = new DOMCdataSection(
<<<CODE_EXAMPLE
<?php
echo "Code example";
?>
CODE_EXAMPLE
);
$prog->append("\n");
$prog->appendChild($code);
$prog->append("\n ");
$example->append("\n ", $prog);
$example->append("\n ", $doc->createEntityReference('example.outputs'));
$output = new DOMCdataSection(
<<<OUPUT_EXAMPLE
Code example
OUPUT_EXAMPLE
);
$screen = $doc->createElement('screen');
$screen->append("\n");
$screen->appendChild($output);
$screen->append("\n ");
$example->append(
"\n ",
$screen,
"\n ",
);
$refSec->append(
$example,
"\n ",
);
return $refSec;
}
/**
* @param array<string, FuncInfo> $funcMap
* @param array<string, FuncInfo> $aliasMap
@@ -5155,6 +5580,9 @@ if ($replacePredefinedConstants && $locationCount < 2) {
if ($replaceClassSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-classsynopses ./ ../doc-en/\n");
}
if ($generateMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --generate-methodsynopses ./ ../doc-en/\n");
}
if ($replaceMethodSynopses && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/\n");
}
@@ -5162,7 +5590,7 @@ if ($verifyManual && $locationCount < 2) {
die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --verify-manual ./ ../doc-en/\n");
}
$manualTarget = null;
if ($replacePredefinedConstants || $replaceClassSynopses || $replaceMethodSynopses || $verifyManual) {
if ($replacePredefinedConstants || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses || $verifyManual) {
$manualTarget = array_pop($locations);
}
if ($locations === []) {
@@ -5361,16 +5789,14 @@ if ($replaceClassSynopses || $verifyManual) {
}
if ($generateMethodSynopses) {
$methodSynopsesDirectory = getcwd() . "/methodsynopses";
$methodSynopses = generateMethodSynopses($funcMap, $aliasMap);
if (!empty($methodSynopses)) {
if (!file_exists($methodSynopsesDirectory)) {
mkdir($methodSynopsesDirectory);
}
if (!file_exists($manualTarget)) {
mkdir($manualTarget);
}
foreach ($methodSynopses as $filename => $content) {
if (file_put_contents("$methodSynopsesDirectory/$filename", $content)) {
foreach ($methodSynopses as $filename => $content) {
if (!file_exists("$manualTarget/$filename")) {
if (file_put_contents("$manualTarget/$filename", $content)) {
echo "Saved $filename\n";
}
}