diff options
author | Topi Reinio <topi.reinio@qt.io> | 2024-03-07 11:27:46 +0000 |
---|---|---|
committer | Topi Reinio <topi.reinio@qt.io> | 2024-04-19 14:46:05 +0000 |
commit | 540ae68c6e2784a0825c39c9d28eb4d8dac2c53a (patch) | |
tree | 4df740bc04b3e7dbed8f67a5644909c3e3c63db3 | |
parent | 85515520f10d903d5a3286e3482607ca92c1c0cb (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.txt | 1 | ||||
-rw-r--r-- | src/qdoc/qdoc/doc/qdoc-warnings.qdoc | 2 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/clangcodeparser.cpp | 40 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/clangcodeparser.h | 3 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/cppcodeparser.cpp | 25 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/cppcodeparser.h | 4 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/main.cpp | 13 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/parsererror.cpp | 90 | ||||
-rw-r--r-- | src/qdoc/qdoc/src/qdoc/parsererror.h | 26 |
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 |