diff options
author | Topi Reinio <topi.reinio@qt.io> | 2024-03-25 18:55:33 +0000 |
---|---|---|
committer | Topi Reinio <topi.reinio@qt.io> | 2024-04-19 14:46:13 +0000 |
commit | 7c928c80085be39cf2354e27d8cbcee50705fcc9 (patch) | |
tree | d4ecb3f08212cf4017c24c44cbf0f0017966684f | |
parent | 540ae68c6e2784a0825c39c9d28eb4d8dac2c53a (diff) |
qdoc: Delay processing of \relates meta-command arguments
The \relates command is used for C++ entities in the global scope to
associate them with a page-generating Aggregate (class, namespace, or
a header file).
The \relates command is a meta-command, processed after all the
topic commands (in the same source file) are handled. However, if the
\relates command referred an aggregate that does not exist (because
its documentation is in another file, yet to be processed), we ran into
problems: QDoc created a new ProxyNode to serve as the location to
generate the the related node's documentation to. After the proxy node
had been created, all subsequent associations were tied to that proxy,
even after the real aggregate's node had been created and added to the
tree. An output page was generated for the proxy, but it often ended
up being orphaned.
In practice this was only a problem for header file aggregates, as
they are the only 'relatable' node type not constructed by
ClangCodeParser during pre-compiled header generation, but later
during the processing of documentation comments (\headerfile topic).
Recent changes to the order of processing topic and meta-commands
unmasked this issue with the current documentation sources, but the
implementation was always fragile.
To fix, add a new post-processing step that handles \relates arguments
after all sources have been parsed. This must be done before calling
Aggregate::normalizeOverloads() as the related non-member functions
have an effect on overload ordering.
Remove remnants of the undocumented ability to allow multiple \relates
commands in the same topic - it was defunct and can be re-introduced
with a proper implementation.
Fixes: QTBUG-123622
Change-Id: I9b85d9c9447825d17d4a4dd345c811dfecfb2411
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
11 files changed, 152 insertions, 35 deletions
diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.cpp b/src/qdoc/qdoc/src/qdoc/aggregate.cpp index fc5a042c6..bf210ba17 100644 --- a/src/qdoc/qdoc/src/qdoc/aggregate.cpp +++ b/src/qdoc/qdoc/src/qdoc/aggregate.cpp @@ -12,6 +12,8 @@ #include "sharedcommentnode.h" #include <vector> +using namespace Qt::Literals::StringLiterals; + QT_BEGIN_NAMESPACE /*! @@ -216,6 +218,40 @@ void Aggregate::markUndocumentedChildrenInternal() } /*! + Adopts each non-aggregate C++ node (function/macro, typedef, enum, variable, + or a shared comment node with genus Node::CPP) in the global scope to the + aggregate specified in the node's documentation using the \\relates command. + + If the target Aggregate is not found in the primary tree, creates a new + ProxyNode to use as the parent. +*/ +void Aggregate::resolveRelates() +{ + Q_ASSERT(name().isEmpty()); // Must be called on the root namespace + auto *database = QDocDatabase::qdocDB(); + + for (auto *node : m_children) { + if (node->isRelatedNonmember() || node->isAggregate()) + continue; + if (node->genus() != Node::CPP) + continue; + + const auto &relates_args = node->doc().metaCommandArgs("relates"_L1); + if (relates_args.isEmpty()) + continue; + + auto *aggregate = database->findRelatesNode(relates_args[0].first.split("::"_L1)); + if (!aggregate) + aggregate = new ProxyNode(this, relates_args[0].first); + else if (node->parent() == aggregate) + continue; + + aggregate->adoptChild(node); + node->setRelatedNonmember(true); + } +} + +/*! Sorts the lists of overloads in the function map and assigns overload numbers. diff --git a/src/qdoc/qdoc/src/qdoc/aggregate.h b/src/qdoc/qdoc/src/qdoc/aggregate.h index 39ab5ca29..a02633e04 100644 --- a/src/qdoc/qdoc/src/qdoc/aggregate.h +++ b/src/qdoc/qdoc/src/qdoc/aggregate.h @@ -30,6 +30,7 @@ public: FunctionNode *findFunctionChild(const QString &name, const Parameters ¶meters); FunctionNode *findFunctionChild(const FunctionNode *clone); + void resolveRelates(); void normalizeOverloads(); void markUndocumentedChildrenInternal(); diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp index 777127873..82bc09064 100644 --- a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp +++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp @@ -409,41 +409,17 @@ void CppCodeParser::processMetaCommand(const Doc &doc, const QString &command, } } } else if (command == COMMAND_RELATES) { - QStringList path = arg.split("::"); - Aggregate *aggregate = database->findRelatesNode(path); - if (aggregate == nullptr) - aggregate = new ProxyNode(node->root(), arg); - - if (node->parent() == aggregate) { // node is already a child of aggregate - doc.location().warning(QStringLiteral("Invalid '\\%1' (already a member of '%2')") - .arg(COMMAND_RELATES, arg)); - } else { - if (node->isAggregate()) { - doc.location().warning(QStringLiteral("Invalid '\\%1' not allowed in '\\%2'") - .arg(COMMAND_RELATES, node->nodeTypeString())); - } else if (!node->isRelatedNonmember() && - !node->parent()->isNamespace() && !node->parent()->isHeader()) { - if (!doc.isInternal()) { - doc.location().warning(QStringLiteral("Invalid '\\%1' ('%2' must be global)") - .arg(COMMAND_RELATES, node->name())); - } - } else if (!node->isRelatedNonmember() && !node->parent()->isHeader()) { - aggregate->adoptChild(node); - node->setRelatedNonmember(true); - } else { - /* - There are multiple \relates commands. This - one is not the first, so clone the node as - a child of aggregate. - */ - Node *clone = node->clone(aggregate); - if (clone == nullptr) { - doc.location().warning( - QStringLiteral("Invalid '\\%1' (multiple uses not allowed in '%2')") - .arg(COMMAND_RELATES, node->nodeTypeString())); - } else { - clone->setRelatedNonmember(true); - } + // REMARK: Generates warnings only; Node instances are + // adopted from the root namespace to other Aggregates + // in a post-processing step, Aggregate::resolveRelates(), + // after all topic commands are processed. + if (node->isAggregate()) { + doc.location().warning("Invalid '\\%1' not allowed in '\\%2'"_L1 + .arg(COMMAND_RELATES, node->nodeTypeString())); + } else if (!node->isRelatedNonmember() && node->parent()->isClassNode()) { + if (!doc.isInternal()) { + doc.location().warning("Invalid '\\%1' ('%2' must be global)"_L1 + .arg(COMMAND_RELATES, node->name())); } } } else if (command == COMMAND_NEXTPAGE) { diff --git a/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp index 9d811a881..57e88fbde 100644 --- a/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp +++ b/src/qdoc/qdoc/src/qdoc/qdocdatabase.cpp @@ -897,6 +897,7 @@ void QDocDatabase::resolveStuff() // order matters primaryTree()->resolveBaseClasses(primaryTreeRoot()); primaryTree()->resolvePropertyOverriddenFromPtrs(primaryTreeRoot()); + primaryTreeRoot()->resolveRelates(); primaryTreeRoot()->normalizeOverloads(); primaryTree()->markDontDocumentNodes(); primaryTree()->removePrivateAndInternalBases(primaryTreeRoot()); diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp new file mode 100644 index 000000000..c2dd411d7 --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "a.h" + +/*! + \typealias foo + \relates <Bar> + + Related to a header file whose source might be parsed later. +*/ diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h new file mode 100644 index 000000000..40c4add88 --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/a.h @@ -0,0 +1,5 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#pragma once + +using foo = int; diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp new file mode 100644 index 000000000..7e3df2f24 --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/b.cpp @@ -0,0 +1,12 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +/*! + \module Module +*/ + +/*! + \headerfile <Bar> + \title A header + \inmodule Module +*/ diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html new file mode 100644 index 000000000..b8dc9412a --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/bar.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> +<!-- b.cpp --> + <title><Bar> - A header | RelatesOrdering</title> +</head> +<body> +<div class="sidebar"> +<div class="toc"> +<h3 id="toc">Contents</h3> +</div> +<div class="sidebar-content" id="sidebar-content"></div></div> +<h1 class="title" translate="no"><Bar> - A header</h1> +<div class="table"><table class="alignedsummary" translate="no"> +<tr><td class="memItemLeft rightAlign topAlign"> Header:</td><td class="memItemRight bottomAlign"> <span class="preprocessor">#include <Bar></span></td></tr> +</table></div> +<h2 id="types">Types</h2> +<div class="table"><table class="alignedsummary" translate="no"> +<tr><td class="memItemLeft rightAlign topAlign"> </td><td class="memItemRight bottomAlign"><b><a href="bar.html#foo-typedef" translate="no">foo</a></b></td></tr> +</table></div> +<!-- $$$<Bar>-description --> +<div class="descr"> +<h2 id="details">Detailed Description</h2> +</div> +<!-- @@@<Bar> --> +<div class="types"> +<h2>Type Documentation</h2> +<!-- $$$foo --> +<h3 class="fn" translate="no" id="foo-typedef"><code class="details extra" translate="no">[alias]</code> <span class="name">foo</span></h3> +<p>Related to a header file whose source might be parsed later.</p> +<!-- @@@foo --> +</div> +</body> +</html> diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html new file mode 100644 index 000000000..3f123509c --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/module-module.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> +<!-- b.cpp --> + <title>RelatesOrdering</title> +</head> +<body> +<div class="sidebar"> +<div class="toc"> +<h3 id="toc">Contents</h3> +<ul> +<li class="level1"><a href="#details">Detailed Description</a></li> +</ul> +</div> +<div class="sidebar-content" id="sidebar-content"></div></div> +<!-- $$$Module-description --> +<div class="descr" id="details"> +</div> +<!-- @@@Module --> +</body> +</html> diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index new file mode 100644 index 000000000..905c657aa --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/expected/relatesordering.index @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE QDOCINDEX> +<INDEX url="" title="RelatesOrdering Reference Documentation" version="" project="RelatesOrdering"> + <namespace name="" status="active" access="public" module="relatesordering"> + <header name="<Bar>" href="bar.html" status="active" documented="true" module="Module" title="A header" fulltitle="<Bar> - A header" subtitle=""> + <typedef name="foo" href="bar.html#foo-typedef" status="active" access="public" location="a.h" related="0" documented="true" aliasedtype="int"/> + </header> + <typedef name="foo" href="bar.html#foo-typedef" status="active" access="public" location="a.h" related="0" documented="true" aliasedtype="int"/> + <module name="Module" href="module-module.html" status="active" documented="true" seen="true" title=""/> + </namespace> +</INDEX> diff --git a/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf new file mode 100644 index 000000000..b943729a3 --- /dev/null +++ b/src/qdoc/qdoc/tests/validateqdocoutputfiles/testdata/relatesordering/relatesordering.qdocconf @@ -0,0 +1,7 @@ +project = RelatesOrdering + +{sourcedirs,headerdirs} = . + +locationinfo = false +warninglimit = 0 +warninglimit.enabled = true |