/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $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$ ** ****************************************************************************/ /* cppcodeparser.cpp */ #include "cppcodeparser.h" #include "config.h" #include "qdocdatabase.h" #include "generator.h" #include #include #include #include #include QT_BEGIN_NAMESPACE /* qmake ignore Q_OBJECT */ QStringList CppCodeParser::exampleFiles; QStringList CppCodeParser::exampleDirs; QSet CppCodeParser::excludeDirs; QSet CppCodeParser::excludeFiles; static QSet topicCommands_; static QSet metaCommands_; /*! The constructor initializes some regular expressions and initializes the tokenizer variables. */ CppCodeParser::CppCodeParser() { if (topicCommands_.isEmpty()) { topicCommands_ << COMMAND_CLASS << COMMAND_DITAMAP << COMMAND_DONTDOCUMENT << COMMAND_ENUM << COMMAND_EXAMPLE << COMMAND_EXTERNALPAGE << COMMAND_FN << COMMAND_GROUP << COMMAND_HEADERFILE << COMMAND_MACRO << COMMAND_MODULE << COMMAND_NAMESPACE << COMMAND_PAGE << COMMAND_PROPERTY << COMMAND_TYPEALIAS << COMMAND_TYPEDEF << COMMAND_VARIABLE << COMMAND_QMLTYPE << COMMAND_QMLPROPERTY << COMMAND_QMLPROPERTYGROUP // mws 13/03/2019 << COMMAND_QMLATTACHEDPROPERTY << COMMAND_QMLSIGNAL << COMMAND_QMLATTACHEDSIGNAL << COMMAND_QMLMETHOD << COMMAND_QMLATTACHEDMETHOD << COMMAND_QMLBASICTYPE << COMMAND_QMLMODULE << COMMAND_JSTYPE << COMMAND_JSPROPERTY << COMMAND_JSPROPERTYGROUP // mws 13/03/2019 << COMMAND_JSATTACHEDPROPERTY << COMMAND_JSSIGNAL << COMMAND_JSATTACHEDSIGNAL << COMMAND_JSMETHOD << COMMAND_JSATTACHEDMETHOD << COMMAND_JSBASICTYPE << COMMAND_JSMODULE << COMMAND_STRUCT << COMMAND_UNION; } if (metaCommands_.isEmpty()) { metaCommands_ = commonMetaCommands(); metaCommands_ << COMMAND_CONTENTSPAGE << COMMAND_INHEADERFILE << COMMAND_NEXTPAGE << COMMAND_OVERLOAD << COMMAND_PREVIOUSPAGE << COMMAND_QMLINSTANTIATES << COMMAND_REIMP << COMMAND_RELATES; } } /*! The constructor initializes a map of special node types for identifying important nodes. And it initializes some filters for identifying and excluding certain kinds of files. */ void CppCodeParser::initializeParser() { CodeParser::initializeParser(); /* All these can appear in a C++ namespace. Don't add anything that can't be in a C++ namespace. */ nodeTypeMap_.insert(COMMAND_NAMESPACE, Node::Namespace); nodeTypeMap_.insert(COMMAND_CLASS, Node::Class); nodeTypeMap_.insert(COMMAND_STRUCT, Node::Struct); nodeTypeMap_.insert(COMMAND_UNION, Node::Union); nodeTypeMap_.insert(COMMAND_ENUM, Node::Enum); nodeTypeMap_.insert(COMMAND_TYPEALIAS, Node::Typedef); nodeTypeMap_.insert(COMMAND_TYPEDEF, Node::Typedef); nodeTypeMap_.insert(COMMAND_PROPERTY, Node::Property); nodeTypeMap_.insert(COMMAND_VARIABLE, Node::Variable); nodeTypeTestFuncMap_.insert(COMMAND_NAMESPACE, &Node::isNamespace); nodeTypeTestFuncMap_.insert(COMMAND_CLASS, &Node::isClassNode); nodeTypeTestFuncMap_.insert(COMMAND_STRUCT, &Node::isStruct); nodeTypeTestFuncMap_.insert(COMMAND_UNION, &Node::isUnion); nodeTypeTestFuncMap_.insert(COMMAND_ENUM, &Node::isEnumType); nodeTypeTestFuncMap_.insert(COMMAND_TYPEALIAS, &Node::isTypedef); nodeTypeTestFuncMap_.insert(COMMAND_TYPEDEF, &Node::isTypedef); nodeTypeTestFuncMap_.insert(COMMAND_PROPERTY, &Node::isProperty); nodeTypeTestFuncMap_.insert(COMMAND_VARIABLE, &Node::isVariable); Config &config = Config::instance(); exampleFiles = config.getCanonicalPathList(CONFIG_EXAMPLES); exampleDirs = config.getCanonicalPathList(CONFIG_EXAMPLEDIRS); QStringList exampleFilePatterns = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_FILEEXTENSIONS); // Used for excluding dirs and files from the list of example files const auto &excludeDirsList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS); excludeDirs = QSet(excludeDirsList.cbegin(), excludeDirsList.cend()); const auto &excludeFilesList = config.getCanonicalPathList(CONFIG_EXCLUDEDIRS); excludeFiles = QSet(excludeFilesList.cbegin(), excludeFilesList.cend()); if (!exampleFilePatterns.isEmpty()) exampleNameFilter = exampleFilePatterns.join(' '); else exampleNameFilter = "*.cpp *.h *.js *.xq *.svg *.xml *.dita *.ui"; QStringList exampleImagePatterns = config.getStringList(CONFIG_EXAMPLES + Config::dot + CONFIG_IMAGEEXTENSIONS); if (!exampleImagePatterns.isEmpty()) exampleImageFilter = exampleImagePatterns.join(' '); else exampleImageFilter = "*.png"; } /*! Clear the map of common node types and call the same function in the base class. */ void CppCodeParser::terminateParser() { nodeTypeMap_.clear(); nodeTypeTestFuncMap_.clear(); excludeDirs.clear(); excludeFiles.clear(); CodeParser::terminateParser(); } /*! Returns a list of extensions for header files. */ QStringList CppCodeParser::headerFileNameFilter() { return QStringList(); } /*! Returns a list of extensions for source files, i.e. not header files. */ QStringList CppCodeParser::sourceFileNameFilter() { return QStringList(); } /*! Returns the set of strings reopresenting the topic commands. */ const QSet &CppCodeParser::topicCommands() { return topicCommands_; } /*! Process the topic \a command found in the \a doc with argument \a arg. */ Node *CppCodeParser::processTopicCommand(const Doc &doc, const QString &command, const ArgLocPair &arg) { ExtraFuncData extra; if (command == COMMAND_FN) { Q_UNREACHABLE(); } else if (nodeTypeMap_.contains(command)) { /* We should only get in here if the command refers to something that can appear in a C++ namespace, i.e. a class, another namespace, an enum, a typedef, a property or a variable. I think these are handled this way to allow the writer to refer to the entity without including the namespace qualifier. */ Node::NodeType type = nodeTypeMap_[command]; QStringList words = arg.first.split(QLatin1Char(' ')); QStringList path; int idx = 0; Node *node = nullptr; if (type == Node::Variable && words.size() > 1) idx = words.size() - 1; path = words[idx].split("::"); node = qdb_->findNodeInOpenNamespace(path, nodeTypeTestFuncMap_[command]); if (node == nullptr) node = qdb_->findNodeByNameAndType(path, nodeTypeTestFuncMap_[command]); if (node == nullptr) { if (isWorthWarningAbout(doc)) { doc.location().warning( tr("Cannot find '%1' specified with '\\%2' in any header file") .arg(arg.first) .arg(command)); } } else if (node->isAggregate()) { if (type == Node::Namespace) { NamespaceNode *ns = static_cast(node); ns->markSeen(); ns->setWhereDocumented(ns->tree()->camelCaseModuleName()); } /* This treats a class as a namespace. */ if ((type == Node::Class) || (type == Node::Namespace) || (type == Node::Struct) || (type == Node::Union)) { if (path.size() > 1) { path.pop_back(); QString ns = path.join(QLatin1String("::")); qdb_->insertOpenNamespace(ns); } } } return node; } else if (command == COMMAND_EXAMPLE) { if (Config::generateExamples) { ExampleNode *en = new ExampleNode(qdb_->primaryTreeRoot(), arg.first); en->setLocation(doc.startLocation()); setExampleFileLists(en); return en; } } else if (command == COMMAND_EXTERNALPAGE) { ExternalPageNode *epn = new ExternalPageNode(qdb_->primaryTreeRoot(), arg.first); epn->setLocation(doc.startLocation()); return epn; } else if (command == COMMAND_HEADERFILE) { HeaderNode *hn = new HeaderNode(qdb_->primaryTreeRoot(), arg.first); hn->setLocation(doc.startLocation()); return hn; } else if (command == COMMAND_GROUP) { CollectionNode *cn = qdb_->addGroup(arg.first); cn->setLocation(doc.startLocation()); cn->markSeen(); return cn; } else if (command == COMMAND_MODULE) { CollectionNode *cn = qdb_->addModule(arg.first); cn->setLocation(doc.startLocation()); cn->markSeen(); return cn; } else if (command == COMMAND_QMLMODULE) { QStringList blankSplit = arg.first.split(QLatin1Char(' ')); CollectionNode *cn = qdb_->addQmlModule(blankSplit[0]); cn->setLogicalModuleInfo(blankSplit); cn->setLocation(doc.startLocation()); cn->markSeen(); return cn; } else if (command == COMMAND_JSMODULE) { QStringList blankSplit = arg.first.split(QLatin1Char(' ')); CollectionNode *cn = qdb_->addJsModule(blankSplit[0]); cn->setLogicalModuleInfo(blankSplit); cn->setLocation(doc.startLocation()); cn->markSeen(); return cn; } else if (command == COMMAND_PAGE) { Node::PageType ptype = Node::ArticlePage; QStringList args = arg.first.split(QLatin1Char(' ')); if (args.size() > 1) { QString t = args[1].toLower(); if (t == "howto") ptype = Node::HowToPage; else if (t == "api") ptype = Node::ApiPage; else if (t == "example") ptype = Node::ExamplePage; else if (t == "overview") ptype = Node::OverviewPage; else if (t == "tutorial") ptype = Node::TutorialPage; else if (t == "faq") ptype = Node::FAQPage; else if (t == "attribution") ptype = Node::AttributionPage; } PageNode *pn = new PageNode(qdb_->primaryTreeRoot(), args[0], ptype); pn->setLocation(doc.startLocation()); return pn; } else if (command == COMMAND_QMLTYPE) { QmlTypeNode *qcn = nullptr; Node *candidate = qdb_->primaryTreeRoot()->findChildNode(arg.first, Node::QML); if (candidate != nullptr && candidate->isQmlType()) qcn = static_cast(candidate); else qcn = new QmlTypeNode(qdb_->primaryTreeRoot(), arg.first); qcn->setLocation(doc.startLocation()); return qcn; } else if (command == COMMAND_JSTYPE) { QmlTypeNode *qcn = nullptr; Node *candidate = qdb_->primaryTreeRoot()->findChildNode(arg.first, Node::JS); if (candidate != nullptr && candidate->isJsType()) qcn = static_cast(candidate); else qcn = new QmlTypeNode(qdb_->primaryTreeRoot(), arg.first, Node::JsType); qcn->setLocation(doc.startLocation()); return qcn; } else if (command == COMMAND_QMLBASICTYPE) { QmlBasicTypeNode *n = new QmlBasicTypeNode(qdb_->primaryTreeRoot(), arg.first); n->setLocation(doc.startLocation()); return n; } else if (command == COMMAND_JSBASICTYPE) { QmlBasicTypeNode *n = new QmlBasicTypeNode(qdb_->primaryTreeRoot(), arg.first, Node::JsBasicType); n->setLocation(doc.startLocation()); return n; } else if ((command == COMMAND_QMLSIGNAL) || (command == COMMAND_QMLMETHOD) || (command == COMMAND_QMLATTACHEDSIGNAL) || (command == COMMAND_QMLATTACHEDMETHOD) || (command == COMMAND_JSSIGNAL) || (command == COMMAND_JSMETHOD) || (command == COMMAND_JSATTACHEDSIGNAL) || (command == COMMAND_JSATTACHEDMETHOD)) { Q_UNREACHABLE(); } return nullptr; } /*! A QML property argument has the form... :: :::: This function splits the argument into one of those two forms. The three part form is the old form, which was used before the creation of Qt Quick 2 and Qt Components. A is the QML equivalent of a C++ namespace. So this function splits \a arg on "::" and stores the parts in \a type, \a module, \a qmlTypeName, and \a name, and returns \c true. If any part other than \a module is not found, a qdoc warning is emitted and false is returned. \note The two QML types \e{Component} and \e{QtObject} never have a module qualifier. */ bool CppCodeParser::splitQmlPropertyArg(const QString &arg, QString &type, QString &module, QString &qmlTypeName, QString &name, const Location &location) { QStringList blankSplit = arg.split(QLatin1Char(' ')); if (blankSplit.size() > 1) { type = blankSplit[0]; QStringList colonSplit(blankSplit[1].split("::")); if (colonSplit.size() == 3) { module = colonSplit[0]; qmlTypeName = colonSplit[1]; name = colonSplit[2]; return true; } if (colonSplit.size() == 2) { module.clear(); qmlTypeName = colonSplit[0]; name = colonSplit[1]; return true; } QString msg = "Unrecognizable QML module/component qualifier for " + arg; location.warning(tr(msg.toLatin1().data())); } else { QString msg = "Missing property type for " + arg; location.warning(tr(msg.toLatin1().data())); } return false; } /*! */ void CppCodeParser::processQmlProperties(const Doc &doc, NodeList &nodes, DocList &docs) { const TopicList &topics = doc.topicsUsed(); if (topics.isEmpty()) return; QString arg; QString type; QString group; QString module; QString property; QString qmlTypeName; Topic topic = topics.at(0); bool jsProps = isJSPropertyTopic(topic.topic); arg = topic.args; if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { int i = property.indexOf('.'); if (i != -1) group = property.left(i); } NodeList sharedNodes; QmlTypeNode *qmlType = qdb_->findQmlType(module, qmlTypeName); if (qmlType == nullptr) qmlType = new QmlTypeNode(qdb_->primaryTreeRoot(), qmlTypeName); for (int i = 0; i < topics.size(); ++i) { QString cmd = topics.at(i).topic; arg = topics.at(i).args; if ((cmd == COMMAND_QMLPROPERTY) || (cmd == COMMAND_QMLATTACHEDPROPERTY) || (cmd == COMMAND_JSPROPERTY) || (cmd == COMMAND_JSATTACHEDPROPERTY)) { bool attached = topics.at(i).topic.contains(QLatin1String("attached")); if (splitQmlPropertyArg(arg, type, module, qmlTypeName, property, doc.location())) { if (qmlType != qdb_->findQmlType(module, qmlTypeName)) { QString msg = tr("All properties in a group must belong to the same type: '%1'") .arg(arg); doc.startLocation().warning(msg); continue; } if (qmlType->hasQmlProperty(property, attached) != nullptr) { QString msg = tr("QML property documented multiple times: '%1'").arg(arg); doc.startLocation().warning(msg); continue; } QmlPropertyNode *qpn = new QmlPropertyNode(qmlType, property, type, attached); qpn->setLocation(doc.startLocation()); qpn->setGenus(jsProps ? Node::JS : Node::QML); nodes.append(qpn); docs.append(doc); sharedNodes << qpn; } } else { doc.startLocation().warning( tr("Command '\\%1'; not allowed with QML/JS property commands").arg(cmd)); } } // Construct a SharedCommentNode (scn) if multiple topics generated // valid nodes. Note that it's important to do this *after* constructing // the topic nodes - which need to be written to index before the related // scn. if (sharedNodes.count() > 1) { SharedCommentNode *scn = new SharedCommentNode(qmlType, sharedNodes.count(), group); scn->setLocation(doc.startLocation()); nodes.append(scn); docs.append(doc); for (const auto n : sharedNodes) scn->append(n); scn->sort(); } } /*! Returns the set of strings representing the common metacommands plus some other metacommands. */ const QSet &CppCodeParser::metaCommands() { return metaCommands_; } /*! Process the metacommand \a command in the context of the \a node associated with the topic command and the \a doc. \a arg is the argument to the metacommand. \a node is guaranteed to be non-null. */ void CppCodeParser::processMetaCommand(const Doc &doc, const QString &command, const ArgLocPair &argLocPair, Node *node) { QString arg = argLocPair.first; if (command == COMMAND_INHEADERFILE) { if (node->isAggregate()) static_cast(node)->addIncludeFile(arg); else doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_INHEADERFILE)); } else if (command == COMMAND_OVERLOAD) { /* Note that this might set the overload flag of the primary function. This is ok because the overload flags and overload numbers will be resolved later in Aggregate::normalizeOverloads(). */ if (node->isFunction()) static_cast(node)->setOverloadFlag(); else if (node->isSharedCommentNode()) static_cast(node)->setOverloadFlags(); else doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_OVERLOAD)); } else if (command == COMMAND_REIMP) { if (node->parent() && !node->parent()->isInternal()) { if (node->isFunction()) { FunctionNode *fn = static_cast(node); // The clang visitor class will have set the // qualified name of the ovverridden function. // If the name of the overridden function isn't // set, issue a warning. if (fn->overridesThis().isEmpty() && isWorthWarningAbout(doc)) { doc.location().warning(tr("Cannot find base function for '\\%1' in %2()") .arg(COMMAND_REIMP) .arg(node->name()), tr("The function either doesn't exist in any " "base class with the same signature or it " "exists but isn't virtual.")); } fn->setReimpFlag(); } else { doc.location().warning( tr("Ignored '\\%1' in %2").arg(COMMAND_REIMP).arg(node->name())); } } } else if (command == COMMAND_RELATES) { QStringList path = arg.split("::"); Aggregate *aggregate = qdb_->findRelatesNode(path); if (aggregate == nullptr) aggregate = new ProxyNode(node->root(), arg); if (node->parent() == aggregate) { // node is already a child of aggregate doc.location().warning( tr("Invalid '\\%1' (already a member of '%2')").arg(COMMAND_RELATES, arg)); } else { if (node->isAggregate()) { doc.location().warning(tr("Invalid '\\%1' not allowed in '\\%2'") .arg(COMMAND_RELATES, node->nodeTypeString())); } else if (!node->isRelatedNonmember() && //!node->parent()->name().isEmpty() && !node->parent()->isNamespace() && !node->parent()->isHeader()) { if (!doc.isInternal()) { doc.location().warning(tr("Invalid '\\%1' ('%2' must be global)") .arg(COMMAND_RELATES, node->name())); } } else if (!node->isRelatedNonmember() && !node->parent()->isHeader()) { aggregate->adoptChild(node); node->setRelatedNonmember(true); } else { /* There are multiple \relates commands. This one is not the first, so clone the node as a child of aggregate. */ Node *clone = node->clone(aggregate); if (clone == nullptr) { doc.location().warning(tr("Invalid '\\%1' (multiple uses not allowed in '%2')") .arg(COMMAND_RELATES, node->nodeTypeString())); } else { clone->setRelatedNonmember(true); } } } } else if (command == COMMAND_CONTENTSPAGE) { setLink(node, Node::ContentsLink, arg); } else if (command == COMMAND_NEXTPAGE) { setLink(node, Node::NextLink, arg); } else if (command == COMMAND_PREVIOUSPAGE) { setLink(node, Node::PreviousLink, arg); } else if (command == COMMAND_STARTPAGE) { setLink(node, Node::StartLink, arg); } else if (command == COMMAND_QMLINHERITS) { if (node->name() == arg) doc.location().warning(tr("%1 tries to inherit itself").arg(arg)); else if (node->isQmlType() || node->isJsType()) { QmlTypeNode *qmlType = static_cast(node); qmlType->setQmlBaseName(arg); } } else if (command == COMMAND_QMLINSTANTIATES) { if (node->isQmlType() || node->isJsType()) { ClassNode *classNode = qdb_->findClassNode(arg.split("::")); if (classNode) node->setClassNode(classNode); else doc.location().warning(tr("C++ class %1 not found: \\instantiates %1").arg(arg)); } else doc.location().warning(tr("\\instantiates is only allowed in \\qmltype")); } else if (command == COMMAND_QMLDEFAULT) { node->markDefault(); } else if (command == COMMAND_QMLREADONLY) { node->markReadOnly(1); } else if ((command == COMMAND_QMLABSTRACT) || (command == COMMAND_ABSTRACT)) { if (node->isQmlType() || node->isJsType()) node->setAbstract(true); } else if (command == COMMAND_DEPRECATED) { node->setStatus(Node::Obsolete); } else if (command == COMMAND_INGROUP || command == COMMAND_INPUBLICGROUP) { // Note: \ingroup and \inpublicgroup are the same (and now recognized as such). qdb_->addToGroup(arg, node); } else if (command == COMMAND_INMODULE) { qdb_->addToModule(arg, node); } else if (command == COMMAND_INQMLMODULE) { qdb_->addToQmlModule(arg, node); } else if (command == COMMAND_INJSMODULE) { qdb_->addToJsModule(arg, node); } else if (command == COMMAND_MAINCLASS) { node->doc().location().warning( tr("'\\mainclass' is deprecated. Consider '\\ingroup mainclasses'")); } else if (command == COMMAND_OBSOLETE) { node->setStatus(Node::Obsolete); } else if (command == COMMAND_NONREENTRANT) { node->setThreadSafeness(Node::NonReentrant); } else if (command == COMMAND_PRELIMINARY) { // \internal wins. if (!node->isInternal()) node->setStatus(Node::Preliminary); } else if (command == COMMAND_INTERNAL) { if (!showInternal()) node->markInternal(); } else if (command == COMMAND_REENTRANT) { node->setThreadSafeness(Node::Reentrant); } else if (command == COMMAND_SINCE) { node->setSince(arg); } else if (command == COMMAND_WRAPPER) { node->setWrapper(); } else if (command == COMMAND_THREADSAFE) { node->setThreadSafeness(Node::ThreadSafe); } else if (command == COMMAND_TITLE) { if (!node->setTitle(arg)) doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_TITLE)); else if (node->isExample()) qdb_->addExampleNode(static_cast(node)); } else if (command == COMMAND_SUBTITLE) { if (!node->setSubtitle(arg)) doc.location().warning(tr("Ignored '\\%1'").arg(COMMAND_SUBTITLE)); } else if (command == COMMAND_QTVARIABLE) { node->setQtVariable(arg); if (!node->isModule() && !node->isQmlModule()) doc.location().warning( tr("Command '\\%1' is only meaningful in '\\module' and '\\qmlmodule'.") .arg(COMMAND_QTVARIABLE)); } else if (command == COMMAND_NOAUTOLIST) { node->setNoAutoList(true); } } /*! The topic command has been processed, and now \a doc and \a node are passed to this function to get the metacommands from \a doc and process them one at a time. \a node is the node where \a doc resides. */ void CppCodeParser::processMetaCommands(const Doc &doc, Node *node) { const QStringList metaCommandsUsed = doc.metaCommandsUsed().values(); for (const auto &command : metaCommandsUsed) { const ArgList args = doc.metaCommandArgs(command); for (const auto &arg : args) processMetaCommand(doc, command, arg, node); } } /*! Parse QML/JS signal/method topic commands. */ FunctionNode *CppCodeParser::parseOtherFuncArg(const QString &topic, const Location &location, const QString &funcArg) { QString funcName; QString returnType; int leftParen = funcArg.indexOf(QChar('(')); if (leftParen > 0) funcName = funcArg.left(leftParen); else funcName = funcArg; int firstBlank = funcName.indexOf(QChar(' ')); if (firstBlank > 0) { returnType = funcName.left(firstBlank); funcName = funcName.right(funcName.length() - firstBlank - 1); } QStringList colonSplit(funcName.split("::")); if (colonSplit.size() < 2) { QString msg = "Unrecognizable QML module/component qualifier for " + funcArg; location.warning(tr(msg.toLatin1().data())); return nullptr; } QString moduleName; QString elementName; if (colonSplit.size() > 2) { moduleName = colonSplit[0]; elementName = colonSplit[1]; } else { elementName = colonSplit[0]; } funcName = colonSplit.last(); Aggregate *aggregate = qdb_->findQmlType(moduleName, elementName); if (aggregate == nullptr) aggregate = qdb_->findQmlBasicType(moduleName, elementName); if (aggregate == nullptr) return nullptr; QString params; QStringList leftParenSplit = funcArg.split('('); if (leftParenSplit.size() > 1) { QStringList rightParenSplit = leftParenSplit[1].split(')'); if (rightParenSplit.size() > 0) params = rightParenSplit[0]; } FunctionNode::Metaness metaness = FunctionNode::getMetanessFromTopic(topic); bool attached = topic.contains(QLatin1String("attached")); FunctionNode *fn = new FunctionNode(metaness, aggregate, funcName, attached); fn->setAccess(Node::Public); fn->setLocation(location); fn->setReturnType(returnType); fn->setParameters(params); return fn; } /*! Parse the macro arguments in \a macroArg ad hoc, without using any actual parser. If successful, return a pointer to the new FunctionNode for the macro. Otherwise return null. \a location is used for reporting errors. */ FunctionNode *CppCodeParser::parseMacroArg(const Location &location, const QString ¯oArg) { QStringList leftParenSplit = macroArg.split('('); if (leftParenSplit.isEmpty()) return nullptr; QString macroName; FunctionNode *oldMacroNode = nullptr; QStringList blankSplit = leftParenSplit[0].split(' '); if (blankSplit.size() > 0) { macroName = blankSplit.last(); oldMacroNode = qdb_->findMacroNode(macroName); } QString returnType; if (blankSplit.size() > 1) { blankSplit.removeLast(); returnType = blankSplit.join(' '); } QString params; if (leftParenSplit.size() > 1) { const QString &afterParen = leftParenSplit.at(1); int rightParen = afterParen.indexOf(')'); if (rightParen >= 0) params = afterParen.left(rightParen); } int i = 0; while (i < macroName.length() && !macroName.at(i).isLetter()) i++; if (i > 0) { returnType += QChar(' ') + macroName.left(i); macroName = macroName.mid(i); } FunctionNode::Metaness metaness = FunctionNode::MacroWithParams; if (params.isEmpty()) metaness = FunctionNode::MacroWithoutParams; FunctionNode *macro = new FunctionNode(metaness, qdb_->primaryTreeRoot(), macroName); macro->setAccess(Node::Public); macro->setLocation(location); macro->setReturnType(returnType); macro->setParameters(params); if (oldMacroNode && macro->compare(oldMacroNode)) { location.warning(tr("\\macro %1 documented more than once").arg(macroArg)); oldMacroNode->doc().location().warning(tr("(The previous doc is here)")); } return macro; } void CppCodeParser::setExampleFileLists(PageNode *pn) { QString examplePath = pn->name(); QString proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".pro"; QString fullPath = Config::findFile(pn->doc().location(), exampleFiles, exampleDirs, proFileName); if (fullPath.isEmpty()) { QString tmp = proFileName; proFileName = examplePath + QLatin1Char('/') + "qbuild.pro"; fullPath = Config::findFile(pn->doc().location(), exampleFiles, exampleDirs, proFileName); if (fullPath.isEmpty()) { proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".qmlproject"; fullPath = Config::findFile(pn->doc().location(), exampleFiles, exampleDirs, proFileName); if (fullPath.isEmpty()) { proFileName = examplePath + QLatin1Char('/') + examplePath.split(QLatin1Char('/')).last() + ".pyproject"; fullPath = Config::findFile(pn->doc().location(), exampleFiles, exampleDirs, proFileName); if (fullPath.isEmpty()) { QString details = QLatin1String("Example directories: ") + exampleDirs.join(QLatin1Char(' ')); if (!exampleFiles.isEmpty()) details += QLatin1String(", example files: ") + exampleFiles.join(QLatin1Char(' ')); pn->location().warning( tr("Cannot find file '%1' or '%2'").arg(tmp).arg(proFileName), details); pn->location().warning(tr(" EXAMPLE PATH DOES NOT EXIST: %1").arg(examplePath), details); return; } } } } int sizeOfBoringPartOfName = fullPath.size() - proFileName.size(); if (fullPath.startsWith("./")) sizeOfBoringPartOfName = sizeOfBoringPartOfName - 2; fullPath.truncate(fullPath.lastIndexOf('/')); QStringList exampleFiles = Config::getFilesHere(fullPath, exampleNameFilter, Location(), excludeDirs, excludeFiles); // Search for all image files under the example project, excluding doc/images directory. QSet excludeDocDirs(excludeDirs); excludeDocDirs.insert(QDir(fullPath).canonicalPath() + "/doc/images"); QStringList imageFiles = Config::getFilesHere(fullPath, exampleImageFilter, Location(), excludeDocDirs, excludeFiles); if (!exampleFiles.isEmpty()) { // move main.cpp and to the end, if it exists QString mainCpp; const auto isGeneratedOrMainCpp = [&mainCpp](const QString &fileName) { if (fileName.endsWith("/main.cpp")) { if (mainCpp.isEmpty()) mainCpp = fileName; return true; } return fileName.contains("/qrc_") || fileName.contains("/moc_") || fileName.contains("/ui_"); }; exampleFiles.erase( std::remove_if(exampleFiles.begin(), exampleFiles.end(), isGeneratedOrMainCpp), exampleFiles.end()); if (!mainCpp.isEmpty()) exampleFiles.append(mainCpp); // add any qmake Qt resource files and qmake project files exampleFiles += Config::getFilesHere(fullPath, "*.qrc *.pro *.qmlproject qmldir"); } for (auto &file : exampleFiles) file = file.mid(sizeOfBoringPartOfName); for (auto &file : imageFiles) file = file.mid(sizeOfBoringPartOfName); ExampleNode *en = static_cast(pn); en->setFiles(exampleFiles); en->setImages(imageFiles); } /*! returns true if \a t is \e {jssignal}, \e {jsmethod}, \e {jsattachedsignal}, or \e {jsattachedmethod}. */ bool CppCodeParser::isJSMethodTopic(const QString &t) { return (t == COMMAND_JSSIGNAL || t == COMMAND_JSMETHOD || t == COMMAND_JSATTACHEDSIGNAL || t == COMMAND_JSATTACHEDMETHOD); } /*! returns true if \a t is \e {qmlsignal}, \e {qmlmethod}, \e {qmlattachedsignal}, or \e {qmlattachedmethod}. */ bool CppCodeParser::isQMLMethodTopic(const QString &t) { return (t == COMMAND_QMLSIGNAL || t == COMMAND_QMLMETHOD || t == COMMAND_QMLATTACHEDSIGNAL || t == COMMAND_QMLATTACHEDMETHOD); } /*! Returns true if \a t is \e {jsproperty}, \e {jspropertygroup}, or \e {jsattachedproperty}. */ bool CppCodeParser::isJSPropertyTopic(const QString &t) { return (t == COMMAND_JSPROPERTY || t == COMMAND_JSATTACHEDPROPERTY); } /*! Returns true if \a t is \e {qmlproperty}, \e {qmlpropertygroup}, or \e {qmlattachedproperty}. */ bool CppCodeParser::isQMLPropertyTopic(const QString &t) { return (t == COMMAND_QMLPROPERTY || t == COMMAND_QMLATTACHEDPROPERTY); } void CppCodeParser::processTopicArgs(const Doc &doc, const QString &topic, NodeList &nodes, DocList &docs) { if (isQMLPropertyTopic(topic) || isJSPropertyTopic(topic)) { processQmlProperties(doc, nodes, docs); } else { ArgList args = doc.metaCommandArgs(topic); Node *node = nullptr; if (args.size() == 1) { if (topic == COMMAND_FN) { if (showInternal() || !doc.isInternal()) node = parserForLanguage("Clang")->parseFnArg(doc.location(), args[0].first); } else if (topic == COMMAND_MACRO) { node = parseMacroArg(doc.location(), args[0].first); } else if (isQMLMethodTopic(topic) || isJSMethodTopic(topic)) { node = parseOtherFuncArg(topic, doc.location(), args[0].first); } else if (topic == COMMAND_DONTDOCUMENT) { qdb_->primaryTree()->addToDontDocumentMap(args[0].first); } else { node = processTopicCommand(doc, topic, args[0]); } if (node != nullptr) { nodes.append(node); docs.append(doc); } } else if (args.size() > 1) { QVector sharedCommentNodes; for (const auto &arg : qAsConst(args)) { node = nullptr; if (topic == COMMAND_FN) { if (showInternal() || !doc.isInternal()) node = parserForLanguage("Clang")->parseFnArg(doc.location(), arg.first); } else if (topic == COMMAND_MACRO) { node = parseMacroArg(doc.location(), arg.first); } else if (isQMLMethodTopic(topic) || isJSMethodTopic(topic)) { node = parseOtherFuncArg(topic, doc.location(), arg.first); } else { node = processTopicCommand(doc, topic, arg); } if (node != nullptr) { bool found = false; for (SharedCommentNode *scn : sharedCommentNodes) { if (scn->parent() == node->parent()) { scn->append(node); found = true; break; } } if (!found) { SharedCommentNode *scn = new SharedCommentNode(node); sharedCommentNodes.append(scn); nodes.append(scn); docs.append(doc); } } } for (auto *scn : sharedCommentNodes) scn->sort(); } } } void CppCodeParser::processMetaCommands(NodeList &nodes, DocList &docs) { QVector::Iterator d = docs.begin(); for (const auto &node : nodes) { if (node != nullptr) { processMetaCommands(*d, node); node->setDoc(*d); checkModuleInclusion(node); if (node->isAggregate()) { Aggregate *aggregate = static_cast(node); if (aggregate->includeFiles().isEmpty()) { Aggregate *parent = aggregate; while (parent->physicalModuleName().isEmpty() && (parent->parent() != nullptr)) parent = parent->parent(); if (parent == aggregate) aggregate->addIncludeFile(aggregate->name()); else aggregate->setIncludeFiles(parent->includeFiles()); } } } ++d; } } bool CppCodeParser::hasTooManyTopics(const Doc &doc) const { QSet topicCommandsUsed = topicCommands() & doc.metaCommandsUsed(); if (topicCommandsUsed.count() > 1) { bool ok = true; for (const auto &t : topicCommandsUsed) { if (!t.startsWith(QLatin1String("qml")) && !t.startsWith(QLatin1String("js"))) ok = false; } if (ok) return false; QString topicList; for (const auto &t : topicCommandsUsed) topicList += QLatin1String(" \\") + t + QLatin1Char(','); topicList[topicList.lastIndexOf(',')] = '.'; int i = topicList.lastIndexOf(','); Q_ASSERT(i >= 0); // we had at least two commas topicList[i] = ' '; topicList.insert(i + 1, "and"); doc.location().warning(tr("Multiple topic commands found in comment:%1").arg(topicList)); return true; } return false; } QT_END_NAMESPACE