summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTopi Reinio <topi.reinio@qt.io>2024-03-07 11:27:46 +0000
committerTopi Reinio <topi.reinio@qt.io>2024-04-19 14:46:05 +0000
commit540ae68c6e2784a0825c39c9d28eb4d8dac2c53a (patch)
tree4df740bc04b3e7dbed8f67a5644909c3e3c63db3
parent85515520f10d903d5a3286e3482607ca92c1c0cb (diff)
qdoc: Collect \fn parsing failures and process them separately
When deciding whether to output a warning when Clang fails to tie an \fn signature to a declaration, information about how the base class is documented is required. Specifically, for classes that are marked \internal and therefore do not generate output in a standard build, QDoc omits subsequent warnings for that class's member functions. This information is no longer available when parsing an \fn topic, as 'internalness' is meta-information that gets assigned to entities in a later step. Introduce a new structure to collect information about parsing failures, and have ClangCodeParser::parseFnArg() return a variant; either a matched Node pointer or an error (FnMatchError). This error contains sufficient context to process it and generate a warning later on. Store these errors that occur during topic command processing into a vector, and handle them after the nodes have also completed meta-command processing. The new mechanism can be utilized for consolidating the handling of multiple types of parsing failures into a central location; for now, start with match failures for \fn. For match failures, improve the logic of resolving the parent of a function from a string representing the function signature; allow both classes and namespaces, and check the entire parent chain for the \internal command. Instead of ad-hoc string parsing, use a regular expression for resolving the scope of an \fn signature. Simplify the phrasing in the warning message related to \fn matching failures. Fixes: QTBUG-123084 Change-Id: I3478774e9141275ad8a043f643850096a146fa1c Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
-rw-r--r--src/qdoc/qdoc/CMakeLists.txt1
-rw-r--r--src/qdoc/qdoc/doc/qdoc-warnings.qdoc2
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp40
-rw-r--r--src/qdoc/qdoc/src/qdoc/clangcodeparser.h3
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp25
-rw-r--r--src/qdoc/qdoc/src/qdoc/cppcodeparser.h4
-rw-r--r--src/qdoc/qdoc/src/qdoc/main.cpp13
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.cpp90
-rw-r--r--src/qdoc/qdoc/src/qdoc/parsererror.h26
9 files changed, 163 insertions, 41 deletions
diff --git a/src/qdoc/qdoc/CMakeLists.txt b/src/qdoc/qdoc/CMakeLists.txt
index 9b25ee7b2..a1d31765e 100644
--- a/src/qdoc/qdoc/CMakeLists.txt
+++ b/src/qdoc/qdoc/CMakeLists.txt
@@ -57,6 +57,7 @@ qt_internal_add_tool(${target_name}
src/qdoc/openedlist.cpp
src/qdoc/pagenode.cpp
src/qdoc/parameters.cpp
+ src/qdoc/parsererror.cpp
src/qdoc/propertynode.cpp
src/qdoc/proxynode.cpp
src/qdoc/puredocparser.cpp
diff --git a/src/qdoc/qdoc/doc/qdoc-warnings.qdoc b/src/qdoc/qdoc/doc/qdoc-warnings.qdoc
index 1633f226d..5dfa52bda 100644
--- a/src/qdoc/qdoc/doc/qdoc-warnings.qdoc
+++ b/src/qdoc/qdoc/doc/qdoc-warnings.qdoc
@@ -123,7 +123,7 @@
that contains character escape sequences, you should enclose the
code in \\c{...} to prevent this warning against the escape sequences.
- \section1 Clang couldn't find function when parsing \\fn <signature>
+ \section1 Failed to find function when parsing \\fn <signature>
When Clang parses a function signature following an \qdoccmd fn command,
it checks this against the declaration in the header file. If Clang
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
index 56a643541..a414b55a3 100644
--- a/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp
@@ -1787,8 +1787,16 @@ ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
Use clang to parse the function signature from a function
command. \a location is used for reporting errors. \a fnSignature
is the string to parse. It is always a function decl.
+ \a idTag is the optional bracketed argument passed to \\fn, or
+ an empty string.
+ \a context is a string list representing the scope (namespaces)
+ under which the function is declared.
+
+ Returns a variant that's either a Node instance tied to the
+ function declaration, or a parsing failure for later processing.
*/
-Node *FnCommandParser::operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
+std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
+ const QString &idTag, QStringList context)
{
Node *fnNode = nullptr;
/*
@@ -1881,36 +1889,12 @@ Node *FnCommandParser::operator()(const Location &location, const QString &fnSig
ClangVisitor visitor(m_qdb, m_allHeaders);
bool ignoreSignature = false;
visitor.visitFnArg(cur, &fnNode, ignoreSignature);
- /*
- If the visitor couldn't find a FunctionNode for the
- signature, then print the clang diagnostics if there
- were any.
- */
- if (fnNode == nullptr) {
+
+ if (!fnNode) {
unsigned diagnosticCount = clang_getNumDiagnostics(tu);
const auto &config = Config::instance();
if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
- bool report = true;
- QStringList signature = fnSignature.split(QChar('('));
- if (signature.size() > 1) {
- QStringList qualifiedName = signature.at(0).split(QChar(' '));
- qualifiedName = qualifiedName.last().split(QLatin1String("::"));
- if (qualifiedName.size() > 1) {
- QString qualifier = qualifiedName.at(0);
- int i = 0;
- while (qualifier.size() > i && !qualifier.at(i).isLetter())
- qualifier[i++] = QChar(' ');
- if (i > 0)
- qualifier = qualifier.simplified();
- ClassNode *cn = m_qdb->findClassNode(QStringList(qualifier));
- if (cn && cn->isInternal())
- report = false;
- }
- }
- if (report) {
- location.warning(
- QStringLiteral("clang couldn't find function when parsing \\fn %1").arg(fnSignature));
- }
+ return FnMatchError{ fnSignature, location };
}
}
}
diff --git a/src/qdoc/qdoc/src/qdoc/clangcodeparser.h b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
index 0ae2bb788..d7568f9a4 100644
--- a/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
+++ b/src/qdoc/qdoc/src/qdoc/clangcodeparser.h
@@ -5,6 +5,7 @@
#define CLANGCODEPARSER_H
#include "codeparser.h"
+#include "parsererror.h"
#include "config.h"
#include <QtCore/qtemporarydir.h>
@@ -49,7 +50,7 @@ struct FnCommandParser {
m_pch{pch}
{}
- Node *operator()(
+ std::variant<Node*, FnMatchError> operator()(
const Location &location,
const QString &fnSignature,
const QString &idTag,
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
index 3523538ac..777127873 100644
--- a/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp
@@ -870,7 +870,8 @@ bool CppCodeParser::isQMLPropertyTopic(const QString &t)
return (t == COMMAND_QMLPROPERTY || t == COMMAND_QMLATTACHEDPROPERTY);
}
-std::vector<TiedDocumentation> CppCodeParser::processTopicArgs(const UntiedDocumentation &untied)
+std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+CppCodeParser::processTopicArgs(const UntiedDocumentation &untied)
{
const Doc &doc = untied.documentation;
@@ -882,6 +883,7 @@ std::vector<TiedDocumentation> CppCodeParser::processTopicArgs(const UntiedDocum
const QString topic = doc.topicsUsed().first().m_topic;
std::vector<TiedDocumentation> tied{};
+ std::vector<FnMatchError> errors{};
if (isQMLPropertyTopic(topic)) {
auto tied_qml = processQmlProperties(untied);
@@ -891,8 +893,13 @@ std::vector<TiedDocumentation> CppCodeParser::processTopicArgs(const UntiedDocum
Node *node = nullptr;
if (args.size() == 1) {
if (topic == COMMAND_FN) {
- if (Config::instance().showInternal() || !doc.isInternal())
- node = fn_parser(doc.location(), args[0].first, args[0].second, untied.context);
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), args[0].first, args[0].second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
} else if (topic == COMMAND_MACRO) {
node = parseMacroArg(doc.location(), args[0].first);
} else if (isQMLMethodTopic(topic)) {
@@ -910,8 +917,13 @@ std::vector<TiedDocumentation> CppCodeParser::processTopicArgs(const UntiedDocum
for (const auto &arg : std::as_const(args)) {
node = nullptr;
if (topic == COMMAND_FN) {
- if (Config::instance().showInternal() || !doc.isInternal())
- node = fn_parser(doc.location(), arg.first, arg.second, untied.context);
+ if (Config::instance().showInternal() || !doc.isInternal()) {
+ auto result = fn_parser(doc.location(), arg.first, arg.second, untied.context);
+ if (auto *error = std::get_if<FnMatchError>(&result))
+ errors.emplace_back(*error);
+ else
+ node = std::get<Node*>(result);
+ }
} else if (topic == COMMAND_MACRO) {
node = parseMacroArg(doc.location(), arg.first);
} else if (isQMLMethodTopic(topic)) {
@@ -939,8 +951,7 @@ std::vector<TiedDocumentation> CppCodeParser::processTopicArgs(const UntiedDocum
scn->sort();
}
}
-
- return tied;
+ return std::make_pair(tied, errors);
}
/*!
diff --git a/src/qdoc/qdoc/src/qdoc/cppcodeparser.h b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
index c4c2c6fff..621dcf3a8 100644
--- a/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
+++ b/src/qdoc/qdoc/src/qdoc/cppcodeparser.h
@@ -6,6 +6,7 @@
#include "clangcodeparser.h"
#include "codeparser.h"
+#include "parsererror.h"
#include "utilities.h"
QT_BEGIN_NAMESPACE
@@ -43,7 +44,8 @@ public:
static bool isQMLMethodTopic(const QString &t);
static bool isQMLPropertyTopic(const QString &t);
- std::vector<TiedDocumentation> processTopicArgs(const UntiedDocumentation &untied);
+ std::pair<std::vector<TiedDocumentation>, std::vector<FnMatchError>>
+ processTopicArgs(const UntiedDocumentation &untied);
void processMetaCommand(const Doc &doc, const QString &command, const ArgPair &argLocPair,
Node *node);
diff --git a/src/qdoc/qdoc/src/qdoc/main.cpp b/src/qdoc/qdoc/src/qdoc/main.cpp
index 9620cb00a..5e48a6a8a 100644
--- a/src/qdoc/qdoc/src/qdoc/main.cpp
+++ b/src/qdoc/qdoc/src/qdoc/main.cpp
@@ -66,6 +66,7 @@ static void parseSourceFiles(
SourceFileParser& source_file_parser,
CppCodeParser& cpp_code_parser
) {
+ ParserErrorHandler error_handler{};
std::stable_sort(sources.begin(), sources.end());
sources.erase (
@@ -79,17 +80,23 @@ static void parseSourceFiles(
});
- std::for_each(qml_sources, sources.end(), [&source_file_parser, &cpp_code_parser](const QString& source){
+ std::for_each(qml_sources, sources.end(),
+ [&source_file_parser, &cpp_code_parser, &error_handler](const QString& source){
qCDebug(lcQdoc, "Parsing %s", qPrintable(source));
auto [untied_documentation, tied_documentation] = source_file_parser(tag_source_file(source));
+ std::vector<FnMatchError> errors{};
for (auto untied : untied_documentation) {
- auto tied = cpp_code_parser.processTopicArgs(untied);
- tied_documentation.insert(tied_documentation.end(), tied.begin(), tied.end());
+ auto result = cpp_code_parser.processTopicArgs(untied);
+ tied_documentation.insert(tied_documentation.end(), result.first.begin(), result.first.end());
};
cpp_code_parser.processMetaCommands(tied_documentation);
+
+ // Process errors that occurred during parsing
+ for (const auto &e : errors)
+ error_handler(e);
});
std::for_each(sources.begin(), qml_sources, [&cpp_code_parser](const QString& source){
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.cpp b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
new file mode 100644
index 000000000..b55660572
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.cpp
@@ -0,0 +1,90 @@
+// 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 "parsererror.h"
+#include "node.h"
+#include "qdocdatabase.h"
+#include "config.h"
+#include "utilities.h"
+
+#include <QtCore/qregularexpression.h>
+
+using namespace Qt::Literals::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class FnMatchError
+ \brief Encapsulates information about \fn match error during parsing.
+*/
+
+/*!
+ \variable FnMatchError::signature
+
+ Signature for the \fn topic that failed to match.
+*/
+
+/*!
+ \relates FnMatchError
+
+ Returns \c true if any parent of a C++ function represented by
+ \a signature is documented as \\internal.
+*/
+bool isParentInternal(const QString &signature)
+{
+ const QRegularExpression scoped_fn{R"((?:\w+(?:<[^>]+>)?::)+~?\w\S*\()"};
+ auto match = scoped_fn.match(signature);
+ if (!match.isValid())
+ return false;
+
+ auto scope = match.captured().split("::"_L1);
+ scope.removeLast(); // Drop function name
+
+ for (auto &s : scope)
+ if (qsizetype pos = s.indexOf('<'); pos >= 0)
+ s.truncate(pos);
+
+ auto parent = QDocDatabase::qdocDB()->findNodeByNameAndType(scope, &Node::isCppNode);
+ if (parent && !(parent->isClassNode() || parent->isNamespace())) {
+ qCDebug(lcQdoc).noquote()
+ << "Invalid scope:" << qPrintable(parent->nodeTypeString())
+ << qPrintable(parent->fullName())
+ << "for \\fn" << qPrintable(signature);
+ return false;
+ }
+
+ while (parent) {
+ if (parent->isInternal())
+ return true;
+ parent = parent->parent();
+ }
+
+ return false;
+}
+
+/*!
+ \class ParserErrorHandler
+ \brief Processes parser errors and outputs warnings for them.
+*/
+
+/*!
+ Generates a warning specific to FnMatchError.
+
+ Warnings for internal documentation are omitted. Specifically, this
+ (omission) happens if:
+
+ \list
+ \li \c {--showinternal} command line option is \b not
+ used, and
+ \li The warning is for an \\fn that is declared
+ under a namespace/class that is documented as
+ \\internal.
+ \endlist
+*/
+void ParserErrorHandler::operator()(const FnMatchError &e) const
+{
+ if (Config::instance().showInternal() || !isParentInternal(e.signature))
+ e.location.warning("Failed to find function when parsing \\fn %1"_L1.arg(e.signature));
+}
+
+QT_END_NAMESPACE
diff --git a/src/qdoc/qdoc/src/qdoc/parsererror.h b/src/qdoc/qdoc/src/qdoc/parsererror.h
new file mode 100644
index 000000000..85435366f
--- /dev/null
+++ b/src/qdoc/qdoc/src/qdoc/parsererror.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#ifndef PARSERERROR_H
+#define PARSERERROR_H
+
+#include "location.h"
+
+#include <QtCore/qstring.h>
+
+QT_BEGIN_NAMESPACE
+
+struct FnMatchError {
+ QString signature {};
+ Location location {};
+
+};
+
+struct ParserErrorHandler
+{
+ void operator()(const FnMatchError &e) const;
+};
+
+QT_END_NAMESPACE
+
+#endif