diff options
| -rw-r--r-- | sources/shiboken6/ApiExtractor/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/classdocumentation.cpp | 414 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/classdocumentation.h | 100 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/docparser.cpp | 3 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/docparser.h | 5 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/messages.cpp | 30 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/messages.h | 10 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/qtdocparser.cpp | 300 | ||||
| -rw-r--r-- | sources/shiboken6/ApiExtractor/qtdocparser.h | 11 |
9 files changed, 655 insertions, 219 deletions
diff --git a/sources/shiboken6/ApiExtractor/CMakeLists.txt b/sources/shiboken6/ApiExtractor/CMakeLists.txt index 1c20a8f2f..48a501b86 100644 --- a/sources/shiboken6/ApiExtractor/CMakeLists.txt +++ b/sources/shiboken6/ApiExtractor/CMakeLists.txt @@ -16,6 +16,7 @@ abstractmetafield.cpp abstractmetafunction.cpp abstractmetatype.cpp abstractmetalang.cpp +classdocumentation.cpp codesniphelpers.cpp conditionalstreamreader.cpp documentation.cpp diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.cpp b/sources/shiboken6/ApiExtractor/classdocumentation.cpp new file mode 100644 index 000000000..3acb131fd --- /dev/null +++ b/sources/shiboken6/ApiExtractor/classdocumentation.cpp @@ -0,0 +1,414 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "classdocumentation.h" +#include "messages.h" + +#include <QtCore/QDebug> +#include <QtCore/QBuffer> +#include <QtCore/QFile> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QXmlStreamAttributes> +#include <QtCore/QXmlStreamWriter> + +#include <algorithm> + +// Sort functions by name and argument count +static bool functionDocumentationLessThan(const FunctionDocumentation &f1, + const FunctionDocumentation &f2) +{ + const int nc = f1.name.compare(f2.name); + if (nc != 0) + return nc < 0; + return f1.parameters.size() < f2.parameters.size(); +} + +static void sortDocumentation(ClassDocumentation *cd) +{ + std::stable_sort(cd->enums.begin(), cd->enums.end(), + [] (const EnumDocumentation &e1, const EnumDocumentation &e2) { + return e1.name < e2.name; }); + std::stable_sort(cd->properties.begin(), cd->properties.end(), + [] (const PropertyDocumentation &p1, const PropertyDocumentation &p2) { + return p1.name < p2.name; }); + std::stable_sort(cd->functions.begin(), cd->functions.end(), + functionDocumentationLessThan); +} + +qsizetype ClassDocumentation::indexOfEnum(const QString &name) const +{ + for (qsizetype i = 0, size = enums.size(); i < size; ++i) { + if (enums.at(i).name == name) + return i; + } + return -1; +} + +FunctionDocumentationList ClassDocumentation::findFunctionCandidates(const QString &name, + bool constant) const +{ + FunctionDocumentationList result; + std::copy_if(functions.cbegin(), functions.cend(), + std::back_inserter(result), + [name, constant](const FunctionDocumentation &fd) { + return fd.constant == constant && fd.name == name; + }); + return result; +} + +static bool matches(const FunctionDocumentation &fd, const FunctionDocumentationQuery &q) +{ + return fd.name == q.name && fd.constant == q.constant && fd.parameters == q.parameters; +} + +qsizetype ClassDocumentation::indexOfFunction(const FunctionDocumentationList &fl, + const FunctionDocumentationQuery &q) +{ + for (qsizetype i = 0, size = fl.size(); i < size; ++i) { + if (matches(fl.at(i), q)) + return i; + } + return -1; +} + +qsizetype ClassDocumentation::indexOfProperty(const QString &name) const +{ + for (qsizetype i = 0, size = properties.size(); i < size; ++i) { + if (properties.at(i).name == name) + return i; + } + return -1; +} + +enum class WebXmlTag +{ + Class, Description, Enum, Function, Parameter, Property, Typedef, Other +}; + +static WebXmlTag tag(QStringView name) +{ + if (name == u"class" || name == u"namespace") + return WebXmlTag::Class; + if (name == u"enum") + return WebXmlTag::Enum; + if (name == u"function") + return WebXmlTag::Function; + if (name == u"description") + return WebXmlTag::Description; + if (name == u"parameter") + return WebXmlTag::Parameter; + if (name == u"property") + return WebXmlTag::Property; + if (name == u"typedef") + return WebXmlTag::Typedef; + return WebXmlTag::Other; +} + +static void parseWebXmlElement(WebXmlTag tag, const QXmlStreamAttributes &attributes, + ClassDocumentation *cd) +{ + switch (tag) { + case WebXmlTag::Class: + cd->name = attributes.value(u"name"_qs).toString(); + break; + case WebXmlTag::Enum: { + EnumDocumentation ed; + ed.name = attributes.value(u"name"_qs).toString(); + cd->enums.append(ed); + } + break; + case WebXmlTag::Function: { + FunctionDocumentation fd; + fd.name = attributes.value(u"name"_qs).toString(); + fd.signature = attributes.value(u"signature"_qs).toString(); + fd.returnType = attributes.value(u"type"_qs).toString(); + fd.constant = attributes.value(u"const"_qs) == u"true"; + cd->functions.append(fd); + } + break; + case WebXmlTag::Parameter: + Q_ASSERT(!cd->functions.isEmpty()); + cd->functions.last().parameters.append(attributes.value(u"type"_qs).toString()); + break; + case WebXmlTag::Property: { + PropertyDocumentation pd; + pd.name = attributes.value(u"name"_qs).toString(); + cd->properties.append(pd); + } + break; + default: + break; + } +} + +// Retrieve the contents of <description> +static QString extractWebXmlDescription(QXmlStreamReader &reader) +{ + QBuffer buffer; + buffer.open(QIODeviceBase::WriteOnly); + QXmlStreamWriter writer(&buffer); + + do { + switch (reader.tokenType()) { + case QXmlStreamReader::StartElement: + writer.writeStartElement(reader.name().toString()); + writer.writeAttributes(reader.attributes()); + break; + case QXmlStreamReader::Characters: + writer.writeCharacters(reader.text().toString()); + break; + case QXmlStreamReader::EndElement: + writer.writeEndElement(); + if (reader.name() == u"description") { + buffer.close(); + return QString::fromUtf8(buffer.buffer()).trimmed(); + } + break; + default: + break; + } + reader.readNext(); + } while (!reader.atEnd()); + + return {}; +} + +static QString msgXmlError(const QString &fileName, const QXmlStreamReader &reader) +{ + QString result; + QTextStream(&result) << fileName << ':' << reader.lineNumber() << ':' + << reader.columnNumber() << ':' << reader.errorString(); + return result; +} + +ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage) +{ + ClassDocumentation result; + + QFile file(fileName); + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + *errorMessage = msgCannotOpenForReading(file); + return result; + } + + WebXmlTag lastTag = WebXmlTag::Other; + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: { + const auto currentTag = tag(reader.name()); + parseWebXmlElement(currentTag, reader.attributes(), &result); + switch (currentTag) { // Store relevant tags in lastTag + case WebXmlTag::Class: + case WebXmlTag::Function: + case WebXmlTag::Enum: + case WebXmlTag::Property: + case WebXmlTag::Typedef: + lastTag = currentTag; + break; + case WebXmlTag::Description: { // Append the description to the element + QString *target = nullptr; + switch (lastTag) { + case WebXmlTag::Class: + target = &result.description; + break; + case WebXmlTag::Function: + target = &result.functions.last().description; + break; + case WebXmlTag::Enum: + target = &result.enums.last().description; + break; + case WebXmlTag::Property: + target = &result.properties.last().description; + default: + break; + } + if (target != nullptr && target->isEmpty()) + *target = extractWebXmlDescription(reader); + } + break; + default: + break; + } + } + default: + break; + } + } + + if (reader.error() != QXmlStreamReader::NoError) { + *errorMessage= msgXmlError(fileName, reader); + return {}; + } + + sortDocumentation(&result); + return result; +} + +QString webXmlModuleDescription(const QString &fileName, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + *errorMessage = msgCannotOpenForReading(file); + return {}; + } + + QString result; + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: + if (reader.name() == u"description") + result = extractWebXmlDescription(reader); + break; + default: + break; + } + } + + if (reader.error() != QXmlStreamReader::NoError) { + *errorMessage= msgXmlError(fileName, reader); + return {}; + } + + return result; +} + +// Debug helpers +template <class T> +static void formatList(QDebug &debug, const char *title, const QList<T> &l) +{ + if (const qsizetype size = l.size()) { + debug << title << '[' << size << "]=("; + for (qsizetype i = 0; i < size; ++i) { + if (i) + debug << ", "; + debug << l.at(i); + } + debug << ')'; + } +} + +static void formatDescription(QDebug &debug, const QString &desc) +{ + debug << "description="; + if (desc.isEmpty()) { + debug << "<empty>"; + return; + } + if (debug.verbosity() < 3) + debug << desc.size() << " chars"; + else + debug << '"' << desc << '"'; +} + +QDebug operator<<(QDebug debug, const EnumDocumentation &e) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Enum("; + if (e.name.isEmpty()) { + debug << "invalid"; + } else { + debug << e.name << ", "; + formatDescription(debug, e.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const PropertyDocumentation &p) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Property("; + if (p.name.isEmpty()) { + debug << "invalid"; + } else { + debug << p.name << ", "; + formatDescription(debug, p.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const FunctionDocumentation &f) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Function("; + if (f.name.isEmpty()) { + debug << "invalid"; + } else { + debug << f.name; + if (!f.returnType.isEmpty()) + debug << ", returns " << f.returnType; + if (f.constant) + debug << ", const"; + formatList(debug, ", parameters", f.parameters); + debug << ", signature=\"" << f.signature << "\", "; + formatDescription(debug, f.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const FunctionDocumentationQuery &q) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "FunctionQuery(" << q.name; + if (q.constant) + debug << ", const"; + formatList(debug, ", parameters", q.parameters); + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const ClassDocumentation &c) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Class("; + if (c) { + debug << c.name << ", "; + formatDescription(debug, c.description); + formatList(debug, ", enums", c.enums); + formatList(debug, ", properties", c.properties); + formatList(debug, ", functions", c.functions); + } else { + debug << "invalid"; + } + debug << ')'; + return debug; +} diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.h b/sources/shiboken6/ApiExtractor/classdocumentation.h new file mode 100644 index 000000000..5b202bbc3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/classdocumentation.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt for Python. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CLASSDOCUMENTATION_H +#define CLASSDOCUMENTATION_H + +#include <QtCore/QStringList> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +/// An enumeration in a WebXML/doxygen document +struct EnumDocumentation +{ + QString name; + QString description; +}; + +/// A QObject property in a WebXML/doxygen document +struct PropertyDocumentation +{ + QString name; + QString description; +}; + +/// Helper struct for querying a function in a WebXML/doxygen document +struct FunctionDocumentationQuery +{ + QString name; + QStringList parameters; + bool constant = false; +}; + +/// A function in a WebXML/doxygen document +struct FunctionDocumentation : public FunctionDocumentationQuery +{ + QString signature; + QString returnType; + QString description; +}; + +using FunctionDocumentationList = QList<FunctionDocumentation>; + +/// A class/namespace in a WebXML/doxygen document +struct ClassDocumentation +{ + qsizetype indexOfEnum(const QString &name) const; + FunctionDocumentationList findFunctionCandidates(const QString &name, + bool constant) const; + static qsizetype indexOfFunction(const FunctionDocumentationList &fl, + const FunctionDocumentationQuery &q); + qsizetype indexOfProperty(const QString &name) const; + + QString name; + QString description; + + QList<EnumDocumentation> enums; + QList<PropertyDocumentation> properties; + FunctionDocumentationList functions; + + operator bool() const { return !name.isEmpty(); } +}; + +/// Parse a WebXML class/namespace document +ClassDocumentation parseWebXml(const QString &fileName, QString *errorMessage); + +/// Extract the module description from a WebXML module document +QString webXmlModuleDescription(const QString &fileName, QString *errorMessage); + +QDebug operator<<(QDebug debug, const EnumDocumentation &e); +QDebug operator<<(QDebug debug, const PropertyDocumentation &p); +QDebug operator<<(QDebug debug, const FunctionDocumentationQuery &q); +QDebug operator<<(QDebug debug, const FunctionDocumentation &f); +QDebug operator<<(QDebug debug, const ClassDocumentation &c); + +#endif // CLASSDOCUMENTATION_H diff --git a/sources/shiboken6/ApiExtractor/docparser.cpp b/sources/shiboken6/ApiExtractor/docparser.cpp index 8c370f409..397544e0c 100644 --- a/sources/shiboken6/ApiExtractor/docparser.cpp +++ b/sources/shiboken6/ApiExtractor/docparser.cpp @@ -26,6 +26,7 @@ ** ****************************************************************************/ #include "docparser.h" +#include "classdocumentation.h" #include "abstractmetaenum.h" #include "abstractmetafield.h" #include "abstractmetafunction.h" @@ -81,6 +82,8 @@ static bool usesRValueReference(const AbstractMetaArgument &a) bool DocParser::skipForQuery(const AbstractMetaFunctionCPtr &func) { // Skip private functions and copies created by AbstractMetaClass::fixFunctions() + // Note: Functions inherited from templates will cause warnings about missing + // documentation, but they should at least be listed. if (!func || func->isPrivate() || func->attributes().testFlag(AbstractMetaFunction::AddedMethod) || func->isModifiedRemoved() diff --git a/sources/shiboken6/ApiExtractor/docparser.h b/sources/shiboken6/ApiExtractor/docparser.h index 206370bca..234dbbc44 100644 --- a/sources/shiboken6/ApiExtractor/docparser.h +++ b/sources/shiboken6/ApiExtractor/docparser.h @@ -40,6 +40,8 @@ class Documentation; class XQuery; +struct FunctionDocumentation; + class DocParser { public: @@ -121,13 +123,14 @@ protected: static AbstractMetaFunctionCList documentableFunctions(const AbstractMetaClass *metaClass); + static QString applyDocModifications(const DocModificationList &mods, const QString &xml); + private: QString m_packageName; QString m_docDataDir; QString m_libSourceDir; static QString execXQuery(const XQueryPtr &xquery, const QString &query) ; - static QString applyDocModifications(const DocModificationList &mods, const QString &xml) ; }; #endif // DOCPARSER_H diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp index 920c5690b..5fe9fb9c7 100644 --- a/sources/shiboken6/ApiExtractor/messages.cpp +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -534,9 +534,11 @@ QString msgCannotFindDocumentation(const QString &fileName, const QString &query) { QString result; - QTextStream(&result) << "Cannot find documentation for " << what - << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName) - << "\n using query:\n " << query; + QTextStream str(&result); + str << "Cannot find documentation for " << what + << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName); + if (!query.isEmpty()) + str << "\n using query:\n " << query; return result; } @@ -545,9 +547,21 @@ QString msgFallbackForDocumentation(const QString &fileName, const QString &query) { QString result; - QTextStream(&result) << "Fallback used while trying to find documentation for " << what - << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName) - << "\n using query:\n " << query; + QTextStream str(&result); + str << "Fallback used while trying to find documentation for " << what + << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName); + if (!query.isEmpty()) + str << "\n using query:\n " << query; + return result; +} + +static QString functionDescription(const AbstractMetaFunction *function) +{ + QString result = u'"' + function->classQualifiedSignature() + u'"'; + if (function->flags().testFlag(AbstractMetaFunction::Flag::HiddenFriend)) + result += u" (hidden friend)"_qs; + if (function->flags().testFlag(AbstractMetaFunction::Flag::InheritedFromTemplate)) + result += u" (inherited from template)"_qs; return result; } @@ -556,7 +570,7 @@ QString msgCannotFindDocumentation(const QString &fileName, const QString &query) { return msgCannotFindDocumentation(fileName, "function", - function->classQualifiedSignature(), query); + functionDescription(function), query); } QString msgFallbackForDocumentation(const QString &fileName, @@ -564,7 +578,7 @@ QString msgFallbackForDocumentation(const QString &fileName, const QString &query) { return msgFallbackForDocumentation(fileName, "function", - function->classQualifiedSignature(), query); + functionDescription(function), query); } QString msgCannotFindDocumentation(const QString &fileName, diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h index b66221792..def68f514 100644 --- a/sources/shiboken6/ApiExtractor/messages.h +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -167,24 +167,24 @@ QString msgUsingMemberClassNotFound(const AbstractMetaClass *c, QString msgCannotFindDocumentation(const QString &fileName, const char *what, const QString &name, - const QString &query); + const QString &query = {}); QString msgFallbackForDocumentation(const QString &fileName, const char *what, const QString &name, - const QString &query); + const QString &query = {}); QString msgCannotFindDocumentation(const QString &fileName, const AbstractMetaFunction *function, - const QString &query); + const QString &query = {}); QString msgFallbackForDocumentation(const QString &fileName, const AbstractMetaFunction *function, - const QString &query); + const QString &query = {}); QString msgCannotFindDocumentation(const QString &fileName, const AbstractMetaClass *metaClass, const AbstractMetaEnum &e, - const QString &query); + const QString &query = {}); QString msgCannotFindDocumentation(const QString &fileName, const AbstractMetaClass *metaClass, diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.cpp b/sources/shiboken6/ApiExtractor/qtdocparser.cpp index fe9609b73..36f83deb5 100644 --- a/sources/shiboken6/ApiExtractor/qtdocparser.cpp +++ b/sources/shiboken6/ApiExtractor/qtdocparser.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "qtdocparser.h" +#include "classdocumentation.h" #include "abstractmetaenum.h" #include "abstractmetafield.h" #include "abstractmetafunction.h" @@ -37,15 +38,13 @@ #include "propertyspec.h" #include "reporthandler.h" #include "typesystem.h" -#include "xmlutils.h" #include <QtCore/QDir> #include <QtCore/QFile> -#include <QtCore/QTextStream> -#include <QtCore/QXmlStreamAttributes> -#include <QtCore/QXmlStreamReader> #include <QUrl> +enum { debugFunctionSearch = 0 }; + static inline QString briefStartElement() { return QStringLiteral("<brief>"); } static inline QString briefEndElement() { return QStringLiteral("</brief>"); } @@ -54,14 +53,6 @@ Documentation QtDocParser::retrieveModuleDocumentation() return retrieveModuleDocumentation(packageName()); } -enum FunctionMatchFlags -{ - MatchArgumentCount = 0x1, - MatchArgumentType = 0x2, - MatchArgumentFuzzyType = 0x4, // Match a "const &" using contains() - DescriptionOnly = 0x8 -}; - static void formatPreQualifications(QTextStream &str, const AbstractMetaType &type) { if (type.isConstant()) @@ -119,123 +110,20 @@ static void formatFunctionUnqualifiedArgTypeQuery(QTextStream &str, } } -static inline void formatFunctionArgTypeQuery(QTextStream &str, const AbstractMetaType &metaType) +static QString formatFunctionArgTypeQuery(const AbstractMetaType &metaType) { - formatPreQualifications(str, metaType); + QString result; + QTextStream str(&result);formatPreQualifications(str, metaType); formatFunctionUnqualifiedArgTypeQuery(str, metaType); formatPostQualifications(str, metaType); -} - -static void formatFunctionArgTypeQuery(QTextStream &str, qsizetype n, - const AbstractMetaType &metaType) -{ - // Fixme: Use arguments.at(i)->type()->originalTypeDescription() - // instead to get unresolved typedefs? - str << "/parameter[" << (n + 1) << "][@type=\""; - formatFunctionArgTypeQuery(str, metaType); - str << "\"]/.."; -} - -// If there is any qualifier like '*', '&', we search by the type as a -// contained word to avoid space mismatches and apparently an issue in -// libxml/xslt that does not match '&' in attributes. -// This should be "matches(type, "^(.*\W)?<type>(\W.*)?$")"), but -// libxslt only supports XPath 1.0. Also note, "\b" is not supported -static void formatFunctionFuzzyArgTypeQuery(QTextStream &str, qsizetype n, - const AbstractMetaType &metaType) -{ - str << "/parameter[" << (n + 1) << "][contains(@type, \""; - formatFunctionUnqualifiedArgTypeQuery(str, metaType); - str << " \")]/.."; // ending with space -} - -static bool tryFuzzyMatching(const AbstractMetaType &metaType) -{ - return metaType.referenceType() != NoReference || metaType.indirections() != 0; -} - -static bool tryFuzzyArgumentMatching(const AbstractMetaArgument &arg) -{ - return tryFuzzyMatching(arg.type()); -} - -static QString functionXQuery(const QString &classQuery, - const AbstractMetaFunctionCPtr &func, - unsigned matchFlags = MatchArgumentCount | MatchArgumentType - | DescriptionOnly) -{ - QString result; - QTextStream str(&result); - const AbstractMetaArgumentList &arguments = func->arguments(); - str << classQuery << "/function[@name=\"" << func->originalName() - << "\" and @const=\"" << (func->isConstant() ? "true" : "false") << '"'; - if (matchFlags & MatchArgumentCount) - str << " and count(parameter)=" << arguments.size(); - str << ']'; - if (!arguments.isEmpty() - && (matchFlags & (MatchArgumentType | MatchArgumentFuzzyType)) != 0) { - for (qsizetype i = 0, size = arguments.size(); i < size; ++i) { - const auto &type = arguments.at(i).type(); - if ((matchFlags & MatchArgumentFuzzyType) != 0 && tryFuzzyMatching(type)) - formatFunctionFuzzyArgTypeQuery(str, i, type); - else - formatFunctionArgTypeQuery(str, i, type); - } - } - if (matchFlags & DescriptionOnly) - str << "/description"; - return result; -} - -static QStringList signaturesFromWebXml(QString w) -{ - QStringList result; - if (w.isEmpty()) - return result; - w.prepend(QLatin1String("<root>")); // Fake root element - w.append(QLatin1String("</root>")); - QXmlStreamReader reader(w); - while (!reader.atEnd()) { - if (reader.readNext() == QXmlStreamReader::StartElement - && reader.name() == QLatin1String("function")) { - result.append(reader.attributes().value(QStringLiteral("signature")).toString()); - } - } return result; } -static QString msgArgumentMatch(const QString &query, const QStringList &matches) -{ - QString result; - QTextStream str(&result); - str << "\n Note: Querying for " << query << " yields "; - if (matches.isEmpty()) - str << "no"; - else - str << matches.size(); - str << " matches"; - if (!matches.isEmpty()) - str << ": \"" << matches.join(QLatin1String("\", \"")) << '"'; - return result; -} - -static inline QString msgArgumentFuzzyTypeMatch(const QStringList &matches) -{ - return msgArgumentMatch(u"arguments using fuzzy types"_qs, matches); -} - -static inline QString msgArgumentCountMatch(const AbstractMetaArgumentList &args, - const QStringList &matches) -{ - return msgArgumentMatch(u"the argument count=="_qs + QString::number(args.size()), matches); -} - QString QtDocParser::queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, const AbstractMetaClass* metaClass, - const QString &classQuery, const AbstractMetaFunctionCPtr &func, const DocModificationList &signedModifs, - const XQueryPtr &xquery, QString *errorMessage) { errorMessage->clear(); @@ -246,75 +134,89 @@ QString QtDocParser::queryFunctionDocumentation(const QString &sourceFileName, funcModifs.append(funcModif); } + const QString docString = + queryFunctionDocumentation(sourceFileName, classDocumentation, metaClass, + func, errorMessage); + + return docString.isEmpty() || funcModifs.isEmpty() + ? docString : applyDocModifications(funcModifs, docString); +} + +QString QtDocParser::queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClass* metaClass, + const AbstractMetaFunctionCPtr &func, + QString *errorMessage) +{ // Properties if (func->isPropertyReader() || func->isPropertyWriter() || func->isPropertyResetter()) { - const auto prop = metaClass->propertySpecs().at(func->propertySpecIndex()); - const QString propertyQuery = classQuery + QLatin1String("/property[@name=\"") - + prop.name() + QLatin1String("\"]/description"); - const QString properyDocumentation = getDocumentation(xquery, propertyQuery, funcModifs); - if (properyDocumentation.isEmpty()) { - *errorMessage = - msgCannotFindDocumentation(sourceFileName, func.data(), propertyQuery); + const QPropertySpec &prop = metaClass->propertySpecs().at(func->propertySpecIndex()); + const auto index = classDocumentation.indexOfProperty(prop.name()); + if (index == -1) { + *errorMessage = msgCannotFindDocumentation(sourceFileName, func.data()); + return {}; } - return properyDocumentation; + return classDocumentation.properties.at(index).description; } - // Query with full match of argument types - const QString fullQuery = functionXQuery(classQuery, func); - const QString result = getDocumentation(xquery, fullQuery, funcModifs); - if (!result.isEmpty()) - return result; - const auto &arguments = func->arguments(); - if (arguments.isEmpty()) { // No arguments, can't be helped - *errorMessage = msgCannotFindDocumentation(sourceFileName, func.data(), fullQuery); - return result; + // Search candidates by name and const-ness + FunctionDocumentationList candidates = + classDocumentation.findFunctionCandidates(func->name(), func->isConstant()); + if (candidates.isEmpty()) { + *errorMessage = msgCannotFindDocumentation(sourceFileName, func.data()) + + u" (no matches)"_qs; + return {}; } - // If there are any "const &" or similar parameters, try fuzzy matching. - // Include the outer <function> element for checking. - if (std::any_of(arguments.cbegin(), arguments.cend(), tryFuzzyArgumentMatching)) { - const unsigned flags = MatchArgumentCount | MatchArgumentFuzzyType; - QString fuzzyArgumentQuery = functionXQuery(classQuery, func, flags); - QStringList signatures = - signaturesFromWebXml(getDocumentation(xquery, fuzzyArgumentQuery, funcModifs)); - if (signatures.size() == 1) { - // One match was found. Repeat the query restricted to the <description> - // element and use the result with a warning. - errorMessage->prepend(msgFallbackForDocumentation(sourceFileName, func.data(), - fullQuery)); - errorMessage->append(u"\n Falling back to \""_qs + signatures.constFirst() - + u"\" obtained by matching fuzzy argument types."_qs); - fuzzyArgumentQuery = functionXQuery(classQuery, func, flags | DescriptionOnly); - return getDocumentation(xquery, fuzzyArgumentQuery, funcModifs); + // Try an exact query + FunctionDocumentationQuery fq; + fq.name = func->name(); + fq.constant = func->isConstant(); + for (const auto &arg : func->arguments()) + fq.parameters.append(formatFunctionArgTypeQuery(arg.type())); + + const auto funcFlags = func->flags(); + // Re-add arguments removed by the metabuilder to binary operator functions + if (funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorLeadingClassArgumentRemoved) + || funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorTrailingClassArgumentRemoved)) { + QString classType = metaClass->qualifiedCppName(); + if (!funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorClassArgumentByValue)) { + classType.prepend(u"const "_qs); + classType.append(u" &"_qs); } + if (funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorLeadingClassArgumentRemoved)) + fq.parameters.prepend(classType); + else + fq.parameters.append(classType); + } - *errorMessage += msgArgumentFuzzyTypeMatch(signatures); + const qsizetype index = ClassDocumentation::indexOfFunction(candidates, fq); - if (signatures.size() > 1) { // Ambiguous, no point in trying argument count - errorMessage->prepend(msgCannotFindDocumentation(sourceFileName, func.data(), - fullQuery)); - return result; - } + if (debugFunctionSearch) { + qDebug() << __FUNCTION__ << metaClass->name() << fq << funcFlags << "returns" + << index << "\n " << candidates.value(index) << "\n " << candidates; } - // Finally, test whether some mismatch in argument types occurred by checking for - // the argument count only. - QString countOnlyQuery = functionXQuery(classQuery, func, MatchArgumentCount); - QStringList signatures = - signaturesFromWebXml(getDocumentation(xquery, countOnlyQuery, funcModifs)); - if (signatures.size() == 1) { - // One match was found. Repeat the query restricted to the <description> - // element and use the result with a warning. - countOnlyQuery = functionXQuery(classQuery, func, MatchArgumentCount | DescriptionOnly); - errorMessage->prepend(msgFallbackForDocumentation(sourceFileName, func.data(), fullQuery)); - errorMessage->append(QLatin1String("\n Falling back to \"") + signatures.constFirst() - + QLatin1String("\" obtained by matching the argument count only.")); - return getDocumentation(xquery, countOnlyQuery, funcModifs); + if (index != -1) + return candidates.at(index).description; + + // Fallback: Try matching by argument count + const auto parameterCount = func->arguments().size(); + auto pend = std::remove_if(candidates.begin(), candidates.end(), + [parameterCount](const FunctionDocumentation &fd) { + return fd.parameters.size() != parameterCount; }); + candidates.erase(pend, candidates.end()); + if (candidates.size() == 1) { + const auto &match = candidates.constFirst(); + QTextStream(errorMessage) << msgFallbackForDocumentation(sourceFileName, func.data()) + << "\n Falling back to \"" << match.signature + << "\" obtained by matching the argument count only."; + return candidates.constFirst().description; } - errorMessage->prepend(msgCannotFindDocumentation(sourceFileName, func.data(), fullQuery)); - *errorMessage += msgArgumentCountMatch(arguments, signatures); - return result; + QTextStream(errorMessage) << msgCannotFindDocumentation(sourceFileName, func.data()) + << " (" << candidates.size() << " candidates matching the argument count)"; + return {}; } // Extract the <brief> section from a WebXML (class) documentation and remove it @@ -364,20 +266,13 @@ void QtDocParser::fillDocumentation(AbstractMetaClass* metaClass) const QString sourceFileName = sourceFile.absoluteFilePath(); QString errorMessage; - XQueryPtr xquery = XQuery::create(sourceFileName, &errorMessage); - if (xquery.isNull()) { + + ClassDocumentation classDocumentation = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentation) { qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); return; } - QString className = metaClass->name(); - - // Class/Namespace documentation - const QString classQuery = QLatin1String("/WebXML/document/") - + (metaClass->isNamespace() ? QLatin1String("namespace") : QLatin1String("class")) - + QLatin1String("[@name=\"") + className + QLatin1String("\"]"); - QString query = classQuery + QLatin1String("/description"); - DocModificationList signedModifs, classModifs; const DocModificationList &mods = metaClass->typeEntry()->docModifications(); for (const DocModification &docModif : mods) { @@ -387,10 +282,12 @@ void QtDocParser::fillDocumentation(AbstractMetaClass* metaClass) signedModifs.append(docModif); } - QString docString = getDocumentation(xquery, query, classModifs); + QString docString = applyDocModifications(mods, classDocumentation.description); + if (docString.isEmpty()) { + QString className = metaClass->name(); qCWarning(lcShibokenDoc, "%s", - qPrintable(msgCannotFindDocumentation(sourceFileName, "class", className, query))); + qPrintable(msgCannotFindDocumentation(sourceFileName, "class", className, {}))); } const QString brief = extractBrief(&docString); @@ -404,8 +301,8 @@ void QtDocParser::fillDocumentation(AbstractMetaClass* metaClass) const auto &funcs = DocParser::documentableFunctions(metaClass); for (const auto &func : funcs) { const QString detailed = - queryFunctionDocumentation(sourceFileName, metaClass, classQuery, - func, signedModifs, xquery, &errorMessage); + queryFunctionDocumentation(sourceFileName, classDocumentation, + metaClass, func, signedModifs, &errorMessage); if (!errorMessage.isEmpty()) qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); const Documentation documentation(detailed, {}); @@ -425,15 +322,15 @@ void QtDocParser::fillDocumentation(AbstractMetaClass* metaClass) #endif // Enums for (AbstractMetaEnum &meta_enum : metaClass->enums()) { - query.clear(); - QTextStream(&query) << classQuery << "/enum[@name=\"" - << meta_enum.name() << "\"]/description"; - doc.setValue(getDocumentation(xquery, query, DocModificationList())); - if (doc.isEmpty()) { + Documentation enumDoc; + const auto index = classDocumentation.indexOfEnum(meta_enum.name()); + if (index != -1) { + enumDoc.setValue(classDocumentation.enums.at(index).description); + meta_enum.setDocumentation(enumDoc); + } else { qCWarning(lcShibokenDoc, "%s", - qPrintable(msgCannotFindDocumentation(sourceFileName, metaClass, meta_enum, query))); + qPrintable(msgCannotFindDocumentation(sourceFileName, metaClass, meta_enum, {}))); } - meta_enum.setDocumentation(doc); } } @@ -467,19 +364,16 @@ Documentation QtDocParser::retrieveModuleDocumentation(const QString& name) } QString errorMessage; - XQueryPtr xquery = XQuery::create(sourceFile, &errorMessage); - if (xquery.isNull()) { + QString docString = webXmlModuleDescription(sourceFile, &errorMessage); + if (!errorMessage.isEmpty()) { qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); return {}; } - // Module documentation - QString query = QLatin1String("/WebXML/document/module[@name=\"") - + moduleName + QLatin1String("\"]/description"); - const QString detailed = getDocumentation(xquery, query, DocModificationList()); - Documentation doc(detailed, {}); + Documentation doc(docString, {}); if (doc.isEmpty()) { - qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(sourceFile, "module", name, query))); + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFile, "module", name))); return doc; } diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.h b/sources/shiboken6/ApiExtractor/qtdocparser.h index 4cc335282..738ffb496 100644 --- a/sources/shiboken6/ApiExtractor/qtdocparser.h +++ b/sources/shiboken6/ApiExtractor/qtdocparser.h @@ -31,6 +31,8 @@ #include "docparser.h" +struct ClassDocumentation; + class QtDocParser : public DocParser { public: @@ -41,11 +43,16 @@ public: private: static QString queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, const AbstractMetaClass* metaClass, - const QString &classQuery, const AbstractMetaFunctionCPtr &func, const DocModificationList &signedModifs, - const XQueryPtr &xquery, + QString *errorMessage); + + static QString queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClass* metaClass, + const AbstractMetaFunctionCPtr &func, QString *errorMessage); }; |
