/**************************************************************************** ** ** Copyright (C) 2016 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 "qtdocparser.h" #include "abstractmetaenum.h" #include "abstractmetafield.h" #include "abstractmetafunction.h" #include "abstractmetalang.h" #include "documentation.h" #include "modifications.h" #include "messages.h" #include "propertyspec.h" #include "reporthandler.h" #include "typesystem.h" #include "xmlutils.h" #include #include #include #include #include #include static inline QString briefStartElement() { return QStringLiteral(""); } static inline QString briefEndElement() { return QStringLiteral(""); } 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()) str << "const " ; } static void formatPostQualifications(QTextStream &str, const AbstractMetaType &type) { if (type.referenceType() == LValueReference) str << " &"; else if (type.referenceType() == RValueReference) str << " &&"; else if (type.indirections()) str << ' ' << QByteArray(type.indirections(), '*'); } static void formatFunctionUnqualifiedArgTypeQuery(QTextStream &str, const AbstractMetaType &metaType) { switch (metaType.typeUsagePattern()) { case AbstractMetaType::FlagsPattern: { // Modify qualified name "QFlags" with name "Alignment" // to "Qt::Alignment" as seen by qdoc. const auto *flagsEntry = static_cast(metaType.typeEntry()); QString name = flagsEntry->qualifiedCppName(); if (name.endsWith(u'>') && name.startsWith(u"QFlags<")) { const int lastColon = name.lastIndexOf(u':'); if (lastColon != -1) { name.replace(lastColon + 1, name.size() - lastColon - 1, metaType.name()); name.remove(0, 7); } else { name = metaType.name(); // QFlags<> of enum in global namespace } } str << name; } break; case AbstractMetaType::ContainerPattern: { // QVector str << metaType.typeEntry()->qualifiedCppName() << '<'; const auto instantiations = metaType.instantiations(); for (int i = 0, size = instantiations.size(); i < size; ++i) { if (i) str << ", "; const auto &instantiation = instantiations.at(i); formatPreQualifications(str, instantiation); str << instantiation.typeEntry()->qualifiedCppName(); formatPostQualifications(str, instantiation); } str << '>'; } break; default: // Fully qualify enums (Qt::AlignmentFlag), nested classes, etc. str << metaType.typeEntry()->qualifiedCppName(); break; } } static inline void formatFunctionArgTypeQuery(QTextStream &str, const AbstractMetaType &metaType) { 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)?(\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("")); // Fake root element w.append(QLatin1String("")); 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 AbstractMetaClass* metaClass, const QString &classQuery, const AbstractMetaFunctionCPtr &func, const DocModificationList &signedModifs, const XQueryPtr &xquery, QString *errorMessage) { errorMessage->clear(); DocModificationList funcModifs; for (const DocModification &funcModif : signedModifs) { if (funcModif.signature() == func->minimalSignature()) funcModifs.append(funcModif); } // 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); } return properyDocumentation; } // 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; } // If there are any "const &" or similar parameters, try fuzzy matching. // Include the outer 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 // 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); } *errorMessage += msgArgumentFuzzyTypeMatch(signatures); if (signatures.size() > 1) { // Ambiguous, no point in trying argument count errorMessage->prepend(msgCannotFindDocumentation(sourceFileName, func.data(), fullQuery)); return result; } } // 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 // 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); } errorMessage->prepend(msgCannotFindDocumentation(sourceFileName, func.data(), fullQuery)); *errorMessage += msgArgumentCountMatch(arguments, signatures); return result; } // Extract the section from a WebXML (class) documentation and remove it // from the source. static QString extractBrief(QString *value) { const auto briefStart = value->indexOf(briefStartElement()); if (briefStart < 0) return {}; const auto briefEnd = value->indexOf(briefEndElement(), briefStart + briefStartElement().size()); if (briefEnd < briefStart) return {}; const auto briefLength = briefEnd + briefEndElement().size() - briefStart; QString briefValue = value->mid(briefStart, briefLength); briefValue.insert(briefValue.size() - briefEndElement().size(), QLatin1String(" More_...")); value->remove(briefStart, briefLength); return briefValue; } void QtDocParser::fillDocumentation(AbstractMetaClass* metaClass) { if (!metaClass) return; const AbstractMetaClass* context = metaClass->enclosingClass(); while(context) { if (context->enclosingClass() == nullptr) break; context = context->enclosingClass(); } QString sourceFileRoot = documentationDataDirectory() + u'/' + metaClass->qualifiedCppName().toLower(); sourceFileRoot.replace(QLatin1String("::"), QLatin1String("-")); QFileInfo sourceFile(sourceFileRoot + QStringLiteral(".webxml")); if (!sourceFile.exists()) sourceFile.setFile(sourceFileRoot + QStringLiteral(".xml")); if (!sourceFile.exists()) { qCWarning(lcShibokenDoc).noquote().nospace() << "Can't find qdoc file for class " << metaClass->name() << ", tried: " << QDir::toNativeSeparators(sourceFile.absoluteFilePath()); return; } const QString sourceFileName = sourceFile.absoluteFilePath(); QString errorMessage; XQueryPtr xquery = XQuery::create(sourceFileName, &errorMessage); if (xquery.isNull()) { 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) { if (docModif.signature().isEmpty()) classModifs.append(docModif); else signedModifs.append(docModif); } QString docString = getDocumentation(xquery, query, classModifs); if (docString.isEmpty()) { qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(sourceFileName, "class", className, query))); } const QString brief = extractBrief(&docString); Documentation doc; if (!brief.isEmpty()) doc.setValue(brief, Documentation::Brief); doc.setValue(docString); metaClass->setDocumentation(doc); //Functions Documentation const auto &funcs = DocParser::documentableFunctions(metaClass); for (const auto &func : funcs) { const QString detailed = queryFunctionDocumentation(sourceFileName, metaClass, classQuery, func, signedModifs, xquery, &errorMessage); if (!errorMessage.isEmpty()) qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); const Documentation documentation(detailed, {}); qSharedPointerConstCast(func)->setDocumentation(documentation); } #if 0 // Fields const AbstractMetaFieldList &fields = metaClass->fields(); for (AbstractMetaField *field : fields) { if (field->isPrivate()) return; QString query = "/doxygen/compounddef/sectiondef/memberdef/name[text()=\"" + field->name() + "\"]/.."; Documentation doc = getDocumentation(DocModificationList(), xquery, query); field->setDocumentation(doc); } #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()) { qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(sourceFileName, metaClass, meta_enum, query))); } meta_enum.setDocumentation(doc); } } static QString qmlReferenceLink(const QFileInfo &qmlModuleFi) { QString result; QTextStream(&result) << "The module also provides QML types."; return result; } Documentation QtDocParser::retrieveModuleDocumentation(const QString& name) { // TODO: This method of acquiring the module name supposes that the target language uses // dots as module separators in package names. Improve this. QString moduleName = name; moduleName.remove(0, name.lastIndexOf(u'.') + 1); const QString prefix = documentationDataDirectory() + u'/' + moduleName.toLower(); QString sourceFile = prefix + QLatin1String(".xml"); if (!QFile::exists(sourceFile)) sourceFile = prefix + QLatin1String("-module.webxml"); if (!QFile::exists(sourceFile)) { qCWarning(lcShibokenDoc).noquote().nospace() << "Can't find qdoc file for module " << name << ", tried: " << QDir::toNativeSeparators(sourceFile); return Documentation(); } QString errorMessage; XQueryPtr xquery = XQuery::create(sourceFile, &errorMessage); if (xquery.isNull()) { 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, {}); if (doc.isEmpty()) { qCWarning(lcShibokenDoc, "%s", qPrintable(msgCannotFindDocumentation(sourceFile, "module", name, query))); return doc; } // If a QML module info file exists, insert a link to the Qt docs. const QFileInfo qmlModuleFi(prefix + QLatin1String("-qmlmodule.webxml")); if (qmlModuleFi.isFile()) { QString docString = doc.detailed(); const int pos = docString.lastIndexOf(u""); if (pos != -1) { docString.insert(pos, qmlReferenceLink(qmlModuleFi)); doc.setDetailed(docString); } } return doc; }