aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sources/shiboken6/ApiExtractor/CMakeLists.txt1
-rw-r--r--sources/shiboken6/ApiExtractor/classdocumentation.cpp414
-rw-r--r--sources/shiboken6/ApiExtractor/classdocumentation.h100
-rw-r--r--sources/shiboken6/ApiExtractor/docparser.cpp3
-rw-r--r--sources/shiboken6/ApiExtractor/docparser.h5
-rw-r--r--sources/shiboken6/ApiExtractor/messages.cpp30
-rw-r--r--sources/shiboken6/ApiExtractor/messages.h10
-rw-r--r--sources/shiboken6/ApiExtractor/qtdocparser.cpp300
-rw-r--r--sources/shiboken6/ApiExtractor/qtdocparser.h11
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 '&amp;' 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);
};