diff options
Diffstat (limited to 'src/qdoc/codeparser.cpp')
-rw-r--r-- | src/qdoc/codeparser.cpp | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/src/qdoc/codeparser.cpp b/src/qdoc/codeparser.cpp new file mode 100644 index 000000000..92a0d5212 --- /dev/null +++ b/src/qdoc/codeparser.cpp @@ -0,0 +1,437 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + codeparser.cpp +*/ + +#include "codeparser.h" +#include "node.h" +#include "tree.h" +#include "config.h" +#include "generator.h" +#include "qdocdatabase.h" +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +#define COMMAND_COMPAT Doc::alias(QLatin1String("compat")) +#define COMMAND_DEPRECATED Doc::alias(QLatin1String("deprecated")) // ### don't document +#define COMMAND_INGROUP Doc::alias(QLatin1String("ingroup")) +#define COMMAND_INMODULE Doc::alias(QLatin1String("inmodule")) // ### don't document +#define COMMAND_INQMLMODULE Doc::alias(QLatin1String("inqmlmodule")) +#define COMMAND_INJSMODULE Doc::alias(QLatin1String("injsmodule")) +#define COMMAND_INTERNAL Doc::alias(QLatin1String("internal")) +#define COMMAND_MAINCLASS Doc::alias(QLatin1String("mainclass")) +#define COMMAND_NONREENTRANT Doc::alias(QLatin1String("nonreentrant")) +#define COMMAND_OBSOLETE Doc::alias(QLatin1String("obsolete")) +#define COMMAND_PAGEKEYWORDS Doc::alias(QLatin1String("pagekeywords")) +#define COMMAND_PRELIMINARY Doc::alias(QLatin1String("preliminary")) +#define COMMAND_INPUBLICGROUP Doc::alias(QLatin1String("inpublicgroup")) +#define COMMAND_QTVARIABLE Doc::alias(QLatin1String("qtvariable")) +#define COMMAND_REENTRANT Doc::alias(QLatin1String("reentrant")) +#define COMMAND_SINCE Doc::alias(QLatin1String("since")) +#define COMMAND_SUBTITLE Doc::alias(QLatin1String("subtitle")) +#define COMMAND_THREADSAFE Doc::alias(QLatin1String("threadsafe")) +#define COMMAND_TITLE Doc::alias(QLatin1String("title")) +#define COMMAND_WRAPPER Doc::alias(QLatin1String("wrapper")) +#define COMMAND_NOAUTOLIST Doc::alias(QLatin1String("noautolist")) + +QList<CodeParser *> CodeParser::parsers; +bool CodeParser::showInternal_ = false; +bool CodeParser::singleExec_ = false; + +/*! + The constructor adds this code parser to the static + list of code parsers. + */ +CodeParser::CodeParser() +{ + qdb_ = QDocDatabase::qdocDB(); + parsers.prepend(this); +} + +/*! + The destructor removes this code parser from the static + list of code parsers. + */ +CodeParser::~CodeParser() +{ + parsers.removeAll(this); +} + +/*! + Initialize the code parser base class. + */ +void CodeParser::initializeParser(const Config& config) +{ + showInternal_ = config.getBool(CONFIG_SHOWINTERNAL); + singleExec_ = config.getBool(CONFIG_SINGLEEXEC); +} + +/*! + Terminating a code parser is trivial. + */ +void CodeParser::terminateParser() +{ + // nothing. +} + +QStringList CodeParser::headerFileNameFilter() +{ + return sourceFileNameFilter(); +} + +void CodeParser::parseHeaderFile(const Location& location, const QString& filePath) +{ + parseSourceFile(location, filePath); +} + +void CodeParser::doneParsingHeaderFiles() +{ + doneParsingSourceFiles(); +} + +/*! + All the code parsers in the static list are initialized here, + after the qdoc configuration variables have been set. + */ +void CodeParser::initialize(const Config& config) +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + (*p)->initializeParser(config); + ++p; + } +} + +/*! + All the code parsers in the static list are terminated here. + */ +void CodeParser::terminate() +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + (*p)->terminateParser(); + ++p; + } +} + +CodeParser *CodeParser::parserForLanguage(const QString& language) +{ + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + if ((*p)->language() == language) + return *p; + ++p; + } + return 0; +} + +CodeParser *CodeParser::parserForHeaderFile(const QString &filePath) +{ + QString fileName = QFileInfo(filePath).fileName(); + + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + + QStringList headerPatterns = (*p)->headerFileNameFilter(); + foreach (const QString &pattern, headerPatterns) { + QRegExp re(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); + if (re.exactMatch(fileName)) + return *p; + } + ++p; + } + return 0; +} + +CodeParser *CodeParser::parserForSourceFile(const QString &filePath) +{ + QString fileName = QFileInfo(filePath).fileName(); + + QList<CodeParser *>::ConstIterator p = parsers.constBegin(); + while (p != parsers.constEnd()) { + + QStringList sourcePatterns = (*p)->sourceFileNameFilter(); + foreach (const QString &pattern, sourcePatterns) { + QRegExp re(pattern, Qt::CaseInsensitive, QRegExp::Wildcard); + if (re.exactMatch(fileName)) + return *p; + } + ++p; + } + return 0; +} + +static QSet<QString> commonMetaCommands_; +/*! + Returns the set of strings representing the common metacommands. + */ +const QSet<QString>& CodeParser::commonMetaCommands() +{ + if (commonMetaCommands_.isEmpty()) { + commonMetaCommands_ << COMMAND_COMPAT + << COMMAND_DEPRECATED + << COMMAND_INGROUP + << COMMAND_INMODULE + << COMMAND_INQMLMODULE + << COMMAND_INTERNAL + << COMMAND_MAINCLASS + << COMMAND_NONREENTRANT + << COMMAND_OBSOLETE + << COMMAND_PAGEKEYWORDS + << COMMAND_PRELIMINARY + << COMMAND_INPUBLICGROUP + << COMMAND_QTVARIABLE + << COMMAND_REENTRANT + << COMMAND_SINCE + << COMMAND_SUBTITLE + << COMMAND_THREADSAFE + << COMMAND_TITLE + << COMMAND_WRAPPER + << COMMAND_INJSMODULE + << COMMAND_NOAUTOLIST; + } + return commonMetaCommands_; +} + +/*! + The topic command has been processed. Now process the other + metacommands that were found. These are not the text markup + commands. + */ +void CodeParser::processCommonMetaCommand(const Location& location, + const QString& command, + const ArgLocPair& arg, + Node* node) +{ + if (command == COMMAND_COMPAT) { + location.warning(tr("\\compat command used, but Qt3 compatibility is no longer supported")); + node->setStatus(Node::Compat); + } + else if (command == COMMAND_DEPRECATED) { + node->setStatus(Node::Obsolete); + } + else if ((command == COMMAND_INGROUP) || (command == COMMAND_INPUBLICGROUP)) { + // Note: \ingroup and \inpublicgroup are now the same. + // Not that they were ever different. + qdb_->addToGroup(arg.first, node); + } + else if (command == COMMAND_INMODULE) { + qdb_->addToModule(arg.first,node); + } + else if (command == COMMAND_INQMLMODULE) { + qdb_->addToQmlModule(arg.first,node); + } + else if (command == COMMAND_INJSMODULE) { + qdb_->addToJsModule(arg.first, 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) { + node->setStatus(Node::Preliminary); + } + else if (command == COMMAND_INTERNAL) { + if (!showInternal_) { + node->setAccess(Node::Private); + node->setStatus(Node::Internal); + if (node->type() == Node::QmlPropertyGroup) { + const QmlPropertyGroupNode* qpgn = static_cast<const QmlPropertyGroupNode*>(node); + NodeList::ConstIterator p = qpgn->childNodes().constBegin(); + while (p != qpgn->childNodes().constEnd()) { + if ((*p)->type() == Node::QmlProperty) { + (*p)->setAccess(Node::Private); + (*p)->setStatus(Node::Internal); + } + ++p; + } + } + } + } + else if (command == COMMAND_REENTRANT) { + node->setThreadSafeness(Node::Reentrant); + } + else if (command == COMMAND_SINCE) { + node->setSince(arg.first); + } + else if (command == COMMAND_WRAPPER) { + node->setWrapper(); + } + else if (command == COMMAND_PAGEKEYWORDS) { + node->addPageKeywords(arg.first); + } + else if (command == COMMAND_THREADSAFE) { + node->setThreadSafeness(Node::ThreadSafe); + } + else if (command == COMMAND_TITLE) { + node->setTitle(arg.first); + if (!node->isDocumentNode() && !node->isCollectionNode()) + location.warning(tr("Ignored '\\%1'").arg(COMMAND_SUBTITLE)); + else if (node->isExample()) + qdb_->addExampleNode(static_cast<ExampleNode*>(node)); + } + else if (command == COMMAND_SUBTITLE) { + node->setSubTitle(arg.first); + if (!node->isDocumentNode() && !node->isCollectionNode()) + location.warning(tr("Ignored '\\%1'").arg(COMMAND_SUBTITLE)); + } + else if (command == COMMAND_QTVARIABLE) { + node->setQtVariable(arg.first); + if (!node->isModule() && !node->isQmlModule()) + location.warning(tr("Command '\\%1' is only meanigfule in '\\module' and '\\qmlmodule'.") + .arg(COMMAND_QTVARIABLE)); + } + else if (command == COMMAND_NOAUTOLIST) { + node->setNoAutoList(true); + } +} + +/*! + \internal + */ +void CodeParser::extractPageLinkAndDesc(const QString& arg, + QString* link, + QString* desc) +{ + QRegExp bracedRegExp(QLatin1String("\\{([^{}]*)\\}(?:\\{([^{}]*)\\})?")); + + if (bracedRegExp.exactMatch(arg)) { + *link = bracedRegExp.cap(1); + *desc = bracedRegExp.cap(2); + if (desc->isEmpty()) + *desc = *link; + } + else { + int spaceAt = arg.indexOf(QLatin1Char(' ')); + if (arg.contains(QLatin1String(".html")) && spaceAt != -1) { + *link = arg.leftRef(spaceAt).trimmed().toString(); + *desc = arg.midRef(spaceAt).trimmed().toString(); + } + else { + *link = arg; + *desc = arg; + } + } +} + +/*! + \internal + */ +void CodeParser::setLink(Node* node, Node::LinkType linkType, const QString& arg) +{ + QString link; + QString desc; + extractPageLinkAndDesc(arg, &link, &desc); + node->setLink(linkType, link, desc); +} + +/*! + Returns \c true if the file being parsed is a .h file. + */ +bool CodeParser::isParsingH() const +{ + return currentFile_.endsWith(".h"); +} + +/*! + Returns \c true if the file being parsed is a .cpp file. + */ +bool CodeParser::isParsingCpp() const +{ + return currentFile_.endsWith(".cpp"); +} + +/*! + Returns \c true if the file being parsed is a .qdoc file. + */ +bool CodeParser::isParsingQdoc() const +{ + return currentFile_.endsWith(".qdoc"); +} + +/*! + For each node that will produce a documentation page, this function + ensures that the node belongs to a module. Normally, the qdoc comment + for an entity that will produce a documentation page will contain an + \inmodule command to tell qdoc which module the entity belongs to. + + But now we normally run qdoc on each module in two passes. The first + produces an index file; the second pass generates the docs after + reading all the index files it needs. + + This means that all the pages generated during each pass 2 run of + qdoc almost certainly belong to a single module, and the name of + that module is, as a rule, used as the project name in the qdocconf + file used when running qdoc on the module. + + So this function first asks if the node \a n has a non-empty module + name. If it it does not have a non-empty module name, it sets the + module name to be the project name. + + In some cases it prints a qdoc warning that it has done this. Namely, + for C++ classes and namespaces. + */ +void CodeParser::checkModuleInclusion(Node* n) +{ + if (n->physicalModuleName().isEmpty()) { + n->setPhysicalModuleName(Generator::defaultModuleName()); + switch (n->type()) { + case Node::Class: + if (n->access() != Node::Private && !n->doc().isEmpty()) { + n->doc().location().warning(tr("Class %1 has no \\inmodule command; " + "using project name by default: %2") + .arg(n->name()).arg(Generator::defaultModuleName())); + } + break; + case Node::Namespace: + if (n->access() != Node::Private && !n->name().isEmpty() && !n->doc().isEmpty()) { + n->doc().location().warning(tr("Namespace %1 has no \\inmodule command; " + "using project name by default: %2") + .arg(n->name()).arg(Generator::defaultModuleName())); + } + break; + default: + break; + } + } +} + +QT_END_NAMESPACE |