aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@qt.io>2021-12-13 14:24:42 +0100
committerFriedemann Kleint <Friedemann.Kleint@qt.io>2021-12-16 15:38:09 +0100
commitb00c9f19ff37f2fed57c780660001f5c1a25a5cb (patch)
treeed167eb1cd1ff9480c477aa166814565078e2475
parent565ce8c4f32822034ffe9ff25d4093c7401d2e6a (diff)
shiboken6/Documentation: Replace XPath/Xquery usage for retrieving WebXML docs by a parser
shiboken used XPath expressions to extract the function descriptions from the qdoc-generated WebXML. With the deprecation of QtXmlPatterns in Qt 6, libxml/xslt was used for this. A major drawback is that libxml/xslt is unable to match entities in attributes which means functions taking parameters by const-ref could not be found. This required cumbersome workarounds involving repeated searches, which still were not sufficient to find all functions. To fix this, write a WebXML parser based on QXmlStreamReader that extracts the contents of a WebXML page into some simple data structures and use those for searching. On this occasion, also use the metafunction flags to correctly match binary operator functions. This reduces the number of functions for which no documentation can be found (from ~1100 to ~800 for the qtbase repository). From the remaining functions, ~230 are functions inherited from templates, which should at least be listed. Others are caused by qdoc not finding hidden friends. The remaining usages of libxml/xslt are the doxygen parser and the documentation modifications using XSLT. Task-number: PYSIDE-1106 Change-Id: I31c045ad42948afad884f40d5bbc8bee7555859e Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
-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);
};