diff options
Diffstat (limited to 'src/qdoc/generator.cpp')
-rw-r--r-- | src/qdoc/generator.cpp | 881 |
1 files changed, 450 insertions, 431 deletions
diff --git a/src/qdoc/generator.cpp b/src/qdoc/generator.cpp index 58d882671..c7831d1fd 100644 --- a/src/qdoc/generator.cpp +++ b/src/qdoc/generator.cpp @@ -42,6 +42,9 @@ #include "separator.h" #include "tokenizer.h" #include "qdocdatabase.h" +#ifndef QT_BOOTSTRAPPED +# include "QtCore/qurl.h" +#endif QT_BEGIN_NAMESPACE @@ -72,7 +75,8 @@ Generator::QDocPass Generator::qdocPass_ = Generator::Neither; bool Generator::qdocSingleExec_ = false; bool Generator::qdocWriteQaPages_ = false; bool Generator::useOutputSubdirs_ = true; -QmlTypeNode* Generator::qmlTypeContext_ = 0; +bool Generator::useTimestamps_ = false; +QmlTypeNode* Generator::qmlTypeContext_ = nullptr; static QRegExp tag("</?@[^>]*>"); static QLatin1String amp("&"); @@ -136,7 +140,7 @@ void Generator::appendFullName(Text& text, const Node *relative, const Node *actualNode) { - if (actualNode == 0) + if (actualNode == nullptr) actualNode = apparentNode; text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) @@ -149,7 +153,7 @@ void Generator::appendFullName(Text& text, const QString& fullName, const Node *actualNode) { - if (actualNode == 0) + if (actualNode == nullptr) actualNode = apparentNode; text << Atom(Atom::LinkNode, CodeMarker::stringForNode(actualNode)) << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) @@ -212,9 +216,7 @@ int Generator::appendSortedNames(Text& text, const ClassNode* cn, const QList<Re r = rc.constBegin(); while (r != rc.constEnd()) { ClassNode* rcn = (*r).node_; - if (rcn && rcn->access() == Node::Public && - rcn->status() != Node::Internal && - !rcn->doc().isEmpty()) { + if (rcn && rcn->isInAPI()) { Text className; appendFullName(className, rcn, cn); classMap[className.toString().toLower()] = className; @@ -273,9 +275,12 @@ void Generator::writeOutFileNames() /*! Creates the file named \a fileName in the output directory. Attaches a QTextStream to the created file, which is written - to all over the place using out(). + to all over the place using out(). This function does not + store the \a fileName in the \a node as the output file name. + + \sa beginSubPage() */ -void Generator::beginSubPage(const Node* node, const QString& fileName) +void Generator::beginFilePage(const Node* node, const QString& fileName) { QString path = outputDir() + QLatin1Char('/'); if (Generator::useOutputSubdirs() && !node->outputSubdirectory().isEmpty() && @@ -297,6 +302,22 @@ void Generator::beginSubPage(const Node* node, const QString& fileName) out->setCodec(outputCodec); #endif outStreamStack.push(out); +} + + /*! + Creates the file named \a fileName in the output directory. + Attaches a QTextStream to the created file, which is written + to all over the place using out(). This function calls another + function, \c beginFilePage(), which is really just most of what + this function used to contain. We needed a different version + that doesn't store the \a fileName in the \a node as the output + file name. + + \sa beginFilePage() + */ +void Generator::beginSubPage(const Node* node, const QString& fileName) +{ + beginFilePage(node, fileName); const_cast<Node*>(node)->setOutputFileName(fileName); } @@ -312,26 +333,65 @@ void Generator::endSubPage() delete outStreamStack.pop(); } +/* + the code below is effectively equivalent to: + input.replace(QRegExp("[^A-Za-z0-9]+"), " "); + input = input.trimmed(); + input.replace(QLatin1Char(' '), QLatin1Char('-')); + input = input.toLower(); + as this function accounted for ~8% of total running time + we optimize a bit... +*/ +static void transmogrify(QString &input, QString &output) +{ + // +5 prevents realloc in fileName() below + output.reserve(input.size() + 5); + bool begun = false; + for (int i = 0; i != input.size(); ++i) { + QChar c = input.at(i); + uint u = c.unicode(); + if (u >= 'A' && u <= 'Z') + u += 'a' - 'A'; + if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) { + output += QLatin1Char(u); + begun = true; + } + else if (begun) { + output += QLatin1Char('-'); + begun = false; + } + } + while (output.endsWith(QLatin1Char('-'))) + output.chop(1); +} + QString Generator::fileBase(const Node *node) const { - if (node->relates()) - node = node->relates(); - else if (!node->isAggregate() && !node->isCollectionNode()) + if (!node->isPageNode() && !node->isCollectionNode()) node = node->parent(); - if (node->type() == Node::QmlPropertyGroup) { - node = node->parent(); - } if (node->hasFileNameBase()) return node->fileNameBase(); QString base; - if (node->isDocumentNode()) { + if (node->isCollectionNode()) { + base = node->name() + outputSuffix(node); + if (base.endsWith(".html")) + base.truncate(base.length() - 5); + + if (node->isQmlModule()) + base.append("-qmlmodule"); + else if (node->isJsModule()) + base.append("-jsmodule"); + else if (node->isModule()) + base.append("-module"); + // Why not add "-group" for group pages? + } else if (node->isTextPageNode()) { base = node->name(); - if (base.endsWith(".html") && !node->isExampleFile()) + if (base.endsWith(".html")) base.truncate(base.length() - 5); - if (node->isExample() || node->isExampleFile()) { + if (node->isExample()) { QString modPrefix(node->physicalModuleName()); if (modPrefix.isEmpty()) { modPrefix = project_; @@ -341,9 +401,8 @@ QString Generator::fileBase(const Node *node) const if (node->isExample()) { base.append(QLatin1String("-example")); } - } - else if (node->isQmlType() || node->isQmlBasicType() || - node->isJsType() || node->isJsBasicType()) { + } else if (node->isQmlType() || node->isQmlBasicType() || + node->isJsType() || node->isJsBasicType()) { base = node->name(); /* To avoid file name conflicts in the html directory, @@ -357,29 +416,15 @@ QString Generator::fileBase(const Node *node) const + QLatin1Char('-')); } base.prepend(outputPrefix(node)); - } - else if (node->isCollectionNode()) { - base = node->name() + outputSuffix(node); - if (base.endsWith(".html")) - base.truncate(base.length() - 5); - - if (node->isQmlModule()) { - base.append("-qmlmodule"); - } - else if (node->isJsModule()) { - base.append("-jsmodule"); - } - else if (node->isModule()) { - base.append("-module"); - } - // Why not add "-group" for group pages? - } - else { + } else if (node->isProxyNode()) { + base = node->name(); + base.append("-proxy"); + } else { const Node *p = node; forever { const Node *pp = p->parent(); base.prepend(p->name()); - if (!pp || pp->name().isEmpty() || pp->isDocumentNode()) + if (pp == nullptr || pp->name().isEmpty() || pp->isTextPageNode()) break; base.prepend(QLatin1Char('-')); p = pp; @@ -393,40 +438,33 @@ QString Generator::fileBase(const Node *node) const } } - // the code below is effectively equivalent to: - // base.replace(QRegExp("[^A-Za-z0-9]+"), " "); - // base = base.trimmed(); - // base.replace(QLatin1Char(' '), QLatin1Char('-')); - // base = base.toLower(); - // as this function accounted for ~8% of total running time - // we optimize a bit... - QString res; - // +5 prevents realloc in fileName() below - res.reserve(base.size() + 5); - bool begun = false; - for (int i = 0; i != base.size(); ++i) { - QChar c = base.at(i); - uint u = c.unicode(); - if (u >= 'A' && u <= 'Z') - u += 'a' - 'A'; - if ((u >= 'a' && u <= 'z') || (u >= '0' && u <= '9')) { - res += QLatin1Char(u); - begun = true; - } - else if (begun) { - res += QLatin1Char('-'); - begun = false; - } - } - while (res.endsWith(QLatin1Char('-'))) - res.chop(1); + transmogrify(base, res); Node* n = const_cast<Node*>(node); n->setFileNameBase(res); return res; } /*! + Constructs an href link from an example file name, which + is a path to the example file. + */ +QString Generator::linkForExampleFile(const QString &path, const Node *parent) +{ + QString link = path; + QString modPrefix(parent->physicalModuleName()); + if (modPrefix.isEmpty()) + modPrefix = project_; + link.prepend(modPrefix.toLower() + QLatin1Char('-')); + + QString res; + transmogrify(link, res); + res.append(QLatin1Char('.')); + res.append(fileExtension()); + return res; +} + +/*! If the \a node has a URL, return the URL as the file name. Otherwise, construct the file name from the fileBase() and either the provided \a extension or fileExtension(), and @@ -509,7 +547,7 @@ QMap<QString, QString>& Generator::formattingRightMap() */ QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) { - if (!node) + if (node == nullptr) return QString(); if (!node->url().isEmpty()) return node->url(); @@ -529,10 +567,10 @@ QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) fdl.append(QLatin1Char('/')); } if (node->isNamespace()) { - - // The root namespace has no name - check for this before creating - // an attribute containing the location of any documentation. - + /* + The root namespace has no name - check for this before creating + an attribute containing the location of any documentation. + */ if (!fileBase(node).isEmpty()) parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); else @@ -553,47 +591,56 @@ QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) QLatin1Char('.') + currentGenerator()->fileExtension(); } } - else if (node->isDocumentNode() || node->isCollectionNode()) { + else if (node->isTextPageNode() || node->isCollectionNode()) { parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); } else if (fileBase(node).isEmpty()) return QString(); - Node *parentNode = 0; + Node *parentNode = nullptr; - if ((parentNode = node->relates())) { - parentName = fullDocumentLocation(node->relates()); - } - else if ((parentNode = node->parent())) { - if (parentNode->isQmlPropertyGroup() || parentNode->isJsPropertyGroup()) { - parentNode = parentNode->parent(); - parentName = fullDocumentLocation(parentNode); - } - else { + if ((parentNode = node->parent())) { + // use the parent's name unless the parent is the root namespace + if (!node->parent()->isNamespace() || !node->parent()->name().isEmpty()) parentName = fullDocumentLocation(node->parent()); - } } - switch (node->type()) { + switch (node->nodeType()) { case Node::Class: + case Node::Struct: + case Node::Union: case Node::Namespace: + case Node::Proxy: parentName = fileBase(node) + QLatin1Char('.') + currentGenerator()->fileExtension(); break; case Node::Function: { const FunctionNode *fn = static_cast<const FunctionNode *>(node); - - if (fn->isDtor()) - anchorRef = "#dtor." + fn->name().mid(1); - - else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) - return fullDocumentLocation(fn->firstAssociatedProperty()); - - else if (fn->overloadNumber() > 0) - anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + switch (fn->metaness()) { + case FunctionNode::JsSignal: + case FunctionNode::QmlSignal: + anchorRef = QLatin1Char('#') + node->name() + "-signal"; + break; + case FunctionNode::JsSignalHandler: + case FunctionNode::QmlSignalHandler: + anchorRef = QLatin1Char('#') + node->name() + "-signal-handler"; + break; + case FunctionNode::JsMethod: + case FunctionNode::QmlMethod: + anchorRef = QLatin1Char('#') + node->name() + "-method"; + break; + default: + if (fn->isDtor()) + anchorRef = "#dtor." + fn->name().mid(1); + else if (fn->hasOneAssociatedProperty() && fn->doc().isEmpty()) + return fullDocumentLocation(fn->firstAssociatedProperty()); + else if (fn->overloadNumber() > 0) + anchorRef = QLatin1Char('#') + cleanRef(fn->name()) + QLatin1Char('-') + QString::number(fn->overloadNumber()); - else - anchorRef = QLatin1Char('#') + cleanRef(fn->name()); + else + anchorRef = QLatin1Char('#') + cleanRef(fn->name()); + break; + } break; } /* @@ -616,28 +663,22 @@ QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) case Node::Property: anchorRef = QLatin1Char('#') + node->name() + "-prop"; break; + case Node::JsProperty: case Node::QmlProperty: if (node->isAttached()) anchorRef = QLatin1Char('#') + node->name() + "-attached-prop"; else anchorRef = QLatin1Char('#') + node->name() + "-prop"; break; - case Node::QmlSignal: - anchorRef = QLatin1Char('#') + node->name() + "-signal"; - break; - case Node::QmlSignalHandler: - anchorRef = QLatin1Char('#') + node->name() + "-signal-handler"; - break; - case Node::QmlMethod: - anchorRef = QLatin1Char('#') + node->name() + "-method"; - break; case Node::Variable: anchorRef = QLatin1Char('#') + node->name() + "-var"; break; + case Node::JsType: case Node::QmlType: - case Node::Document: + case Node::Page: case Node::Group: case Node::Module: + case Node::JsModule: case Node::QmlModule: { parentName = fileBase(node); @@ -649,8 +690,8 @@ QString Generator::fullDocumentLocation(const Node *node, bool useSubdir) break; } - if (!node->isClass() && !node->isNamespace()) { - if (node->status() == Node::Obsolete) + if (!node->isClassNode() && !node->isNamespace()) { + if (node->isObsolete()) parentName.replace(QLatin1Char('.') + currentGenerator()->fileExtension(), "-obsolete." + currentGenerator()->fileExtension()); } @@ -678,20 +719,13 @@ void Generator::generateAlsoList(const Node *node, CodeMarker *marker) } } -int Generator::generateAtom(const Atom * /* atom */, - const Node * /* relative */, - CodeMarker * /* marker */) -{ - return 0; -} - const Atom *Generator::generateAtomList(const Atom *atom, const Node *relative, CodeMarker *marker, bool generate, int &numAtoms) { - while (atom) { + while (atom != nullptr) { if (atom->type() == Atom::FormatIf) { int numAtoms0 = numAtoms; bool rightFormat = canHandleFormat(atom->string()); @@ -700,8 +734,8 @@ const Atom *Generator::generateAtomList(const Atom *atom, marker, generate && rightFormat, numAtoms); - if (!atom) - return 0; + if (atom == nullptr) + return nullptr; if (atom->type() == Atom::FormatElse) { ++numAtoms; @@ -710,8 +744,8 @@ const Atom *Generator::generateAtomList(const Atom *atom, marker, generate && !rightFormat, numAtoms); - if (!atom) - return 0; + if (atom == nullptr) + return nullptr; } if (atom->type() == Atom::FormatEndif) { @@ -742,7 +776,7 @@ const Atom *Generator::generateAtomList(const Atom *atom, atom = atom->next(); } } - return 0; + return nullptr; } /*! @@ -751,20 +785,12 @@ const Atom *Generator::generateAtomList(const Atom *atom, */ void Generator::generateBody(const Node *node, CodeMarker *marker) { - bool quiet = false; - - if (node->type() == Node::Document) { - const DocumentNode *dn = static_cast<const DocumentNode *>(node); - if ((dn->docSubtype() == Node::File) || (dn->docSubtype() == Node::Image)) { - quiet = true; - } - } if (!node->hasDoc() && !node->hasSharedDoc()) { /* Test for special function, like a destructor or copy constructor, that has no documentation. */ - if (node->type() == Node::Function) { + if (node->isFunction()) { const FunctionNode* func = static_cast<const FunctionNode*>(node); if (func->isDtor()) { Text text; @@ -812,32 +838,29 @@ void Generator::generateBody(const Node *node, CodeMarker *marker) generateText(text, node, marker); out() << "</p>"; } - else if (!node->isWrapper() && !quiet && !node->isReimplemented()) { + else if (!node->isWrapper() && !node->isMarkedReimp()) { if (!func->isIgnored()) // undocumented functions added by Q_OBJECT node->location().warning(tr("No documentation for '%1'").arg(node->plainSignature())); } - } - else if (!node->isWrapper() && !quiet && !node->isReimplemented()) { - /* - Don't require documentation of things defined in Q_GADGET - */ + } else if (!node->isWrapper() && !node->isMarkedReimp()) { + // Don't require documentation of things defined in Q_GADGET if (node->name() != QLatin1String("QtGadgetHelper")) node->location().warning(tr("No documentation for '%1'").arg(node->plainSignature())); } } else if (!node->isSharingComment()) { - if (node->type() == Node::Function) { - const FunctionNode *func = static_cast<const FunctionNode *>(node); - if (!func->reimplementedFrom().isEmpty()) - generateReimplementedFrom(func, marker); + if (node->isFunction()) { + const FunctionNode *fn = static_cast<const FunctionNode *>(node); + if (!fn->overridesThis().isEmpty()) + generateReimplementsClause(fn, marker); } if (!generateText(node->doc().body(), node, marker)) { - if (node->isReimplemented()) + if (node->isMarkedReimp()) return; } - if (node->type() == Node::Enum) { + if (node->isEnumType()) { const EnumNode *enume = (const EnumNode *) node; QSet<QString> definedItems; @@ -861,8 +884,6 @@ void Generator::generateBody(const Node *node, CodeMarker *marker) node->doc().location().warning(tr("No such enum item '%1' in %2") .arg(*a).arg(node->plainFullName()), details); - if (*a == "Void") - qDebug() << "VOID:" << node->name() << definedItems; } else if (!documentedItems.contains(*a)) { node->doc().location().warning(tr("Undocumented enum item '%1' in %2") @@ -871,187 +892,179 @@ void Generator::generateBody(const Node *node, CodeMarker *marker) ++a; } } - } - else if (node->type() == Node::Function) { - const FunctionNode *func = static_cast<const FunctionNode *>(node); - QSet<QString> definedParams; - QVector<Parameter>::ConstIterator p = func->parameters().constBegin(); - while (p != func->parameters().constEnd()) { - if (!(*p).name().isEmpty()) - definedParams.insert((*p).name()); - ++p; - } - - QSet<QString> documentedParams = func->doc().parameterNames(); - QSet<QString> allParams = definedParams + documentedParams; - if (allParams.count() > definedParams.count() - || allParams.count() > documentedParams.count()) { - QSet<QString>::ConstIterator a = allParams.constBegin(); - while (a != allParams.constEnd()) { - if (!definedParams.contains(*a)) { + } else if (node->isFunction()) { + const FunctionNode *fn = static_cast<const FunctionNode *>(node); + QSet<QString> declaredNames; + fn->parameters().getNames(declaredNames); + QSet<QString> documentedNames = fn->doc().parameterNames(); + if (declaredNames != documentedNames) { + QSet<QString>::const_iterator i = declaredNames.constBegin(); + while (i != declaredNames.constEnd()) { + if (!documentedNames.contains(*i)) { + if (fn->isActive() || fn->isPreliminary()) { + if (!fn->isMarkedReimp() && !fn->isOverload()) { + fn->doc().location().warning( + tr("Undocumented parameter '%1' in %2") + .arg(*i).arg(node->plainFullName())); + } + } + } + ++i; + } + i = documentedNames.constBegin(); + while (i != documentedNames.constEnd()) { + if (!declaredNames.contains(*i)) { + QString best = nearestName(*i, declaredNames); QString details; - QString best = nearestName(*a, definedParams); if (!best.isEmpty()) details = tr("Maybe you meant '%1'?").arg(best); - - node->doc().location().warning( - tr("No such parameter '%1' in %2").arg(*a).arg(node->plainFullName()), - details); - } - else if (!(*a).isEmpty() && !documentedParams.contains(*a)) { - bool needWarning = (func->status() > Node::Obsolete); - if (func->overloadNumber() > 0) { - FunctionNode *primaryFunc = func->parent()->findFunctionNode(func->name(), QString()); - if (primaryFunc) { - foreach (const Parameter ¶m, - primaryFunc->parameters()) { - if (param.name() == *a) { - needWarning = false; - break; - } - } - } - } - if (needWarning && !func->isReimplemented() && !func->isOverload()) - node->doc().location().warning( - tr("Undocumented parameter '%1' in %2") - .arg(*a).arg(node->plainFullName())); + fn->doc().location().warning(tr("No such parameter '%1' in %2") + .arg(*i).arg(fn->plainFullName()), + details); } - ++a; + ++i; } } /* - Something like this return value check should - be implemented at some point. + This return value check should be implemented + for all functions with a return type. + mws 13/12/2018 */ - if (func->status() > Node::Obsolete && func->returnType() == "bool" - && func->reimplementedFrom() == 0 && !func->isOverload()) { - QString body = func->doc().body().toString(); - if (!body.contains("return", Qt::CaseInsensitive)) - node->doc().location().warning(tr("Undocumented return value")); + if (!fn->isObsolete() && fn->returnsBool() && + !fn->isMarkedReimp() && !fn->isOverload()) { + if (!fn->doc().body().contains("return")) + node->doc().location().warning(tr("Undocumented return value " + "(hint: use 'return' or 'returns' in the text")); } } } - if (node->isDocumentNode()) { - const DocumentNode *dn = static_cast<const DocumentNode *>(node); - if (dn->isExample() && !dn->noAutoList()) { - generateExampleFiles(dn, marker); - } - else if (dn->docSubtype() == Node::File) { - Text text; - Quoter quoter; - Doc::quoteFromFile(dn->doc().location(), quoter, dn->name()); - QString code = quoter.quoteTo(dn->location(), QString(), QString()); - CodeMarker *codeMarker = CodeMarker::markerForFileName(dn->name()); - text << Atom(codeMarker->atomType(), code); - generateText(text, dn, codeMarker); + // For examples, generate either a link to the project directory + // (if url.examples is defined), or a list of files/images. + if (node->isExample()) { + const ExampleNode* en = static_cast<const ExampleNode*>(node); + QString exampleUrl = config()->getString(CONFIG_URL + Config::dot + CONFIG_EXAMPLES); + if (!exampleUrl.isEmpty()) { + generateLinkToExample(en, marker, exampleUrl); + } else if (!en->noAutoList()) { + generateFileList(en, marker, false); + generateFileList(en, marker, true); } } } -void Generator::generateCppReferencePage(Node* /* node */, CodeMarker* /* marker */) +/*! + Generates a link to the project folder for example node \a en. + \a baseUrl is the base URL - path information is available in + the example node's name() and 'examplesinstallpath' configuration + variable. +*/ +void Generator::generateLinkToExample(const ExampleNode *en, + CodeMarker *marker, + const QString &baseUrl) { -} + Text text; + QString exampleUrl(baseUrl); -void Generator::generateExampleFiles(const DocumentNode *dn, CodeMarker *marker) -{ - if (dn->childNodes().isEmpty()) - return; - generateFileList(dn, marker, Node::File); - generateFileList(dn, marker, Node::Image); -} + if (!exampleUrl.contains("\1")) { + if (!exampleUrl.endsWith("/")) + exampleUrl += "/"; + exampleUrl += "\1"; + } -void Generator::generateDocumentNode(DocumentNode* /* dn */, CodeMarker* /* marker */) -{ -} + // Name of the example node is the path, relative to install path + QStringList path = QStringList() + << config()->getString(CONFIG_EXAMPLESINSTALLPATH) + << en->name(); + path.removeAll({}); -void Generator::generateCollectionNode(CollectionNode* , CodeMarker* ) -{ + QString link; +#ifndef QT_BOOTSTRAPPED + link = QUrl(baseUrl).host(); +#endif + if (!link.isEmpty()) + link.prepend(" @ "); + link.prepend("Example project"); + + text << Atom::ParaLeft + << Atom(Atom::Link, exampleUrl.replace("\1", path.join("/"))) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << Atom(Atom::String, link) + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom::ParaRight; + + generateText(text, 0, marker); } /*! - This function is called when the documentation for an - example is being formatted. It outputs the list of source - files comprising the example, and the list of images used - by the example. The images are copied into a subtree of + This function is called when the documentation for an example is + being formatted. It outputs a list of files for the example, which + can be the example's source files or the list of images used by the + example. The images are copied into a subtree of \c{...doc/html/images/used-in-examples/...} - */ -void Generator::generateFileList(const DocumentNode* dn, - CodeMarker* marker, - Node::DocSubtype subtype, - const QString& regExp) +*/ +void Generator::generateFileList(const ExampleNode* en, CodeMarker* marker, bool images) { - int count = 0; Text text; OpenedList openedList(OpenedList::Bullet); QString tag; + QStringList paths; + Atom::AtomType atomType = Atom::ExampleFileLink; - NodeList children(dn->childNodes()); - std::sort(children.begin(), children.end(), Generator::compareNodes); - if (!regExp.isEmpty()) { - QRegExp re(regExp); - QMutableListIterator<Node*> i(children); - while (i.hasNext()) { - if (!re.exactMatch(i.next()->name())) - i.remove(); - } - } - if (children.size() > 1) { - switch (subtype) { - default: - case Node::File: - tag = "Files:"; - break; - case Node::Image: - tag = "Images:"; - break; - } - text << Atom::ParaLeft << tag << Atom::ParaRight; + if (images) { + paths = en->images(); + tag = "Images:"; + atomType = Atom::ExampleImageLink; + } else { //files + paths = en->files(); + tag = "Files:"; } + std::sort(paths.begin(), paths.end(), Generator::comparePaths); + text << Atom::ParaLeft << tag << Atom::ParaRight; text << Atom(Atom::ListLeft, openedList.styleString()); - foreach (const Node* child, children) { - if (child->docSubtype() == subtype) { - ++count; - QString file = child->name(); - if (subtype == Node::Image) { - if (!file.isEmpty()) { - QDir dirInfo; - QString userFriendlyFilePath; - const QString prefix("/images/used-in-examples/"); - QString srcPath = Config::findFile(dn->location(), - QStringList(), - exampleDirs, - file, - exampleImgExts, - &userFriendlyFilePath); - outFileNames_ << prefix.mid(1) + userFriendlyFilePath; - userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); - QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; - if (!dirInfo.mkpath(imgOutDir)) - dn->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir)); - Config::copyFile(dn->location(), srcPath, file, imgOutDir); - } - + QString path; + foreach (QString file, paths) { + if (images) { + if (!file.isEmpty()) { + QDir dirInfo; + QString userFriendlyFilePath; + const QString prefix("/images/used-in-examples/"); + QString srcPath = Config::findFile(en->location(), + QStringList(), + exampleDirs, + file, + exampleImgExts, + &userFriendlyFilePath); + outFileNames_ << prefix.mid(1) + userFriendlyFilePath; + userFriendlyFilePath.truncate(userFriendlyFilePath.lastIndexOf('/')); + QString imgOutDir = outDir_ + prefix + userFriendlyFilePath; + if (!dirInfo.mkpath(imgOutDir)) + en->location().fatal(tr("Cannot create output directory '%1'").arg(imgOutDir)); + Config::copyFile(en->location(), srcPath, file, imgOutDir); } - openedList.next(); - text << Atom(Atom::ListItemNumber, openedList.numberString()) - << Atom(Atom::ListItemLeft, openedList.styleString()) - << Atom::ParaLeft - << Atom(Atom::Link, file) - << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) - << file - << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) - << Atom::ParaRight - << Atom(Atom::ListItemRight, openedList.styleString()); } + else { + generateExampleFilePage(en, file, marker); + } + + openedList.next(); + text << Atom(Atom::ListItemNumber, openedList.numberString()) + << Atom(Atom::ListItemLeft, openedList.styleString()) + << Atom::ParaLeft + << Atom(atomType, file) + << Atom(Atom::FormattingLeft, ATOM_FORMATTING_LINK) + << file + << Atom(Atom::FormattingRight, ATOM_FORMATTING_LINK) + << Atom::ParaRight + << Atom(Atom::ListItemRight, openedList.styleString()); + path = file; } text << Atom(Atom::ListRight, openedList.styleString()); - if (count > 0) - generateText(text, dn, marker); + if (!paths.isEmpty()) + generateText(text, en, marker); } void Generator::generateInheritedBy(const ClassNode *classe, CodeMarker *marker) @@ -1113,19 +1126,7 @@ void Generator::generateDocumentation(Node* node) return; if (node->isInternal() && !showInternal_) return; - - if (node->isDocumentNode()) { - DocumentNode* docNode = static_cast<DocumentNode*>(node); - if (docNode->docSubtype() == Node::ExternalPage) - return; - if (docNode->docSubtype() == Node::Image) - return; - if (docNode->docSubtype() == Node::Page) { - if (docNode->count() > 0) - qDebug("PAGE %s HAS CHILDREN", qPrintable(docNode->title())); - } - } - else if (node->isQmlPropertyGroup() || node->isJsPropertyGroup()) + if (node->isExternalPage()) return; /* @@ -1133,68 +1134,81 @@ void Generator::generateDocumentation(Node* node) */ CodeMarker *marker = CodeMarker::markerForFileName(node->location().filePath()); - if (node->parent() != 0) { - if (node->isClass() || (node->isNamespace() && node->docMustBeGenerated())) { - beginSubPage(node, fileName(node)); - generateCppReferencePage(static_cast<Aggregate*>(node), marker); - endSubPage(); - } - else if (node->isQmlType() || node->isJsType()) { - beginSubPage(node, fileName(node)); - QmlTypeNode* qcn = static_cast<QmlTypeNode*>(node); - generateQmlTypePage(qcn, marker); - endSubPage(); - } - else if (node->isDocumentNode()) { - beginSubPage(node, fileName(node)); - generateDocumentNode(static_cast<DocumentNode*>(node), marker); - endSubPage(); - } - else if (node->isQmlBasicType() || node->isJsBasicType()) { + if (node->parent() != nullptr) { + if (node->isCollectionNode()) { + /* + A collection node collects: groups, C++ modules, + QML modules or JavaScript modules. Testing for a + CollectionNode must be done before testing for a + TextPageNode because a CollectionNode is a PageNode + at this point. + + Don't output an HTML page for the collection + node unless the \group, \module, \qmlmodule or + \jsmodule command was actually seen by qdoc in + the qdoc comment for the node. + + A key prerequisite in this case is the call to + mergeCollections(cn). We must determine whether + this group, module, QML module, or JavaScript + module has members in other modules. We know at + this point that cn's members list contains only + members in the current module. Therefore, before + outputting the page for cn, we must search for + members of cn in the other modules and add them + to the members list. + */ + CollectionNode* cn = static_cast<CollectionNode*>(node); + if (cn->wasSeen()) { + qdb_->mergeCollections(cn); + beginSubPage(node, fileName(node)); + generateCollectionNode(cn, marker); + endSubPage(); + } else if (cn->isGenericCollection()) { + // Currently used only for the module's related orphans page + // but can be generalized for other kinds of collections if + // other use cases pop up. + QString name = cn->name().toLower(); + name.replace(QChar(' '), QString("-")); + QString filename = cn->tree()->physicalModuleName() + "-" + name + "." + fileExtension(); + beginSubPage(node, filename); + generateGenericCollectionPage(cn, marker); + endSubPage(); + } + } else if (node->isTextPageNode()) { beginSubPage(node, fileName(node)); - QmlBasicTypeNode* qbtn = static_cast<QmlBasicTypeNode*>(node); - generateQmlBasicTypePage(qbtn, marker); + generatePageNode(static_cast<PageNode*>(node), marker); endSubPage(); + } else if (node->isAggregate()) { + if ((node->isClassNode() || node->isHeader() || node->isNamespace()) && + node->docMustBeGenerated()) { + beginSubPage(node, fileName(node)); + generateCppReferencePage(static_cast<Aggregate*>(node), marker); + endSubPage(); + } else if (node->isQmlType() || node->isJsType()) { + beginSubPage(node, fileName(node)); + QmlTypeNode* qcn = static_cast<QmlTypeNode*>(node); + generateQmlTypePage(qcn, marker); + endSubPage(); + } else if (node->isQmlBasicType() || node->isJsBasicType()) { + beginSubPage(node, fileName(node)); + QmlBasicTypeNode* qbtn = static_cast<QmlBasicTypeNode*>(node); + generateQmlBasicTypePage(qbtn, marker); + endSubPage(); + } else if (node->isProxyNode()) { + beginSubPage(node, fileName(node)); + generateProxyPage(static_cast<Aggregate*>(node), marker); + endSubPage(); + } } } if (node->isAggregate()) { Aggregate* aggregate = static_cast<Aggregate*>(node); - int i = 0; - while (i < aggregate->childNodes().count()) { - Node *c = aggregate->childNodes().at(i); - if (c->isAggregate() && !c->isPrivate()) { - generateDocumentation((Aggregate*)c); - } - else if (c->isCollectionNode()) { - /* - A collection node collects: groups, C++ modules, - QML modules or JavaScript modules. - - Don't output an HTML page for the collection - node unless the \group, \module, \qmlmodule or - \jsmodule command was actually seen by qdoc in - the qdoc comment for the node. - - A key prerequisite in this case is the call to - mergeCollections(cn). We must determine whether - this group, module, QML module, or JavaScript - module has members in other modules. We know at - this point that cn's members list contains only - members in the current module. Therefore, before - outputting the page for cn, we must search for - members of cn in the other modules and add them - to the members list. - */ - CollectionNode* cn = static_cast<CollectionNode*>(c); - if (cn->wasSeen()) { - qdb_->mergeCollections(cn); - beginSubPage(c, fileName(c)); - generateCollectionNode(cn, marker); - endSubPage(); - } - } - ++i; + const NodeList &children = aggregate->childNodes(); + foreach (Node *n, children) { + if (n->isPageNode() && !n->isPrivate()) + generateDocumentation(n); } } } @@ -1242,13 +1256,6 @@ void Generator::generateQmlInheritedBy(const QmlTypeNode* qcn, } /*! - */ -void Generator::generateQmlInherits(QmlTypeNode* , CodeMarker* ) -{ - // stub. -} - -/*! Extract sections of markup text surrounded by \e qmltext and \e endqmltext and output them. */ @@ -1260,7 +1267,7 @@ bool Generator::generateQmlText(const Text& text, const Atom* atom = text.firstAtom(); bool result = false; - if (atom != 0) { + if (atom != nullptr) { initializeTextOutput(); while (atom) { if (atom->type() != Atom::QmlText) @@ -1279,19 +1286,34 @@ bool Generator::generateQmlText(const Text& text, return result; } -void Generator::generateReimplementedFrom(const FunctionNode *fn, CodeMarker *marker) +void Generator::generateReimplementsClause(const FunctionNode *fn, CodeMarker *marker) { - if (!fn->reimplementedFrom().isEmpty()) { - if (fn->parent()->isClass()) { + if (!fn->overridesThis().isEmpty()) { + if (fn->parent()->isClassNode()) { ClassNode* cn = static_cast<ClassNode*>(fn->parent()); - const FunctionNode *from = cn->findOverriddenFunction(fn); - if (from && from->access() != Node::Private && from->parent()->access() != Node::Private) { + const FunctionNode *overrides = cn->findOverriddenFunction(fn); + if (overrides && !overrides->isPrivate() && !overrides->parent()->isPrivate()) { + if (overrides->hasDoc()) { + Text text; + text << Atom::ParaLeft << "Reimplements: "; + QString fullName = overrides->parent()->name() + "::" + overrides->signature(false, true); + appendFullName(text, overrides->parent(), fullName, overrides); + text << "." << Atom::ParaRight; + generateText(text, fn, marker); + return; + } + } + const PropertyNode* sameName = cn->findOverriddenProperty(fn); + if (sameName && sameName->hasDoc()) { Text text; - text << Atom::ParaLeft << "Reimplemented from "; - QString fullName = from->parent()->name() + "::" + from->name() + "()"; - appendFullName(text, from->parent(), fullName, from); + text << Atom::ParaLeft << "Reimplements an access function for property: "; + QString fullName = sameName->parent()->name() + "::" + sameName->name(); + appendFullName(text, sameName->parent(), fullName, sameName); text << "." << Atom::ParaRight; generateText(text, fn, marker); + } else { + fn->doc().location().warning(tr("Illegal \\reimp; no documented virtual function for %1") + .arg(fn->plainSignature())); } } } @@ -1304,7 +1326,7 @@ void Generator::generateSince(const Node *node, CodeMarker *marker) text << Atom::ParaLeft << "This " << typeString(node); - if (node->type() == Node::Enum) + if (node->isEnumType()) text << " was introduced or modified in "; else text << " was introduced in "; @@ -1415,7 +1437,7 @@ bool Generator::generateText(const Text& text, CodeMarker *marker) { bool result = false; - if (text.firstAtom() != 0) { + if (text.firstAtom() != nullptr) { int numAtoms = 0; initializeTextOutput(); generateAtomList(text.firstAtom(), @@ -1444,30 +1466,28 @@ static bool hasExceptions(const Node* node, { bool result = false; Node::ThreadSafeness ts = node->threadSafeness(); - const Aggregate* a = static_cast<const Aggregate*>(node); - NodeList::ConstIterator c = a->childNodes().constBegin(); - while (c != a->childNodes().constEnd()) { - if (!(*c)->isObsolete()){ - switch ((*c)->threadSafeness()) { + const NodeList &children = static_cast<const Aggregate*>(node)->childNodes(); + foreach (Node *n, children) { + if (!n->isObsolete()){ + switch (n->threadSafeness()) { case Node::Reentrant: - reentrant.append(*c); + reentrant.append(n); if (ts == Node::ThreadSafe) result = true; break; case Node::ThreadSafe: - threadsafe.append(*c); + threadsafe.append(n); if (ts == Node::Reentrant) result = true; break; case Node::NonReentrant: - nonreentrant.append(*c); + nonreentrant.append(n); result = true; break; default: break; } } - ++c; } return result; } @@ -1596,17 +1616,18 @@ void Generator::generateThreadSafeness(const Node *node, CodeMarker *marker) /*! If the node is an overloaded signal, and a node with an example on how to connect to it + + Someone didn't finish writing this comment, and I don't know what this + function is supposed to do, so I have not tried to complete the comment + yet. */ void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker) { - if (node->type() != Node::Function) + if (!node->isFunction()) return; const FunctionNode *func = static_cast<const FunctionNode *>(node); - if (!func->isSignal()) + if (!func->isSignal() || !func->hasOverloads()) return; - if (node->parent()->overloads(node->name()).count() <= 1) - return; - // Compute a friendly name for the object of that instance. // e.g: "QAbstractSocket" -> "abstractSocket" @@ -1622,23 +1643,9 @@ void Generator::generateOverloadedSignal(const Node* node, CodeMarker* marker) // it is very unlikely that we will ever have public API overloading // signals by const. QString code = "connect(" + objectName + ", QOverload<"; - for (int i = 0; i < func->parameters().size(); ++i) { - if (i != 0) - code += ", "; - code += func->parameters().at(i).dataType(); - } - + func->parameters().getTypeList(code); code += ">::of(&" + func->parent()->name() + "::" + func->name() + "),\n [=]("; - - for (int i = 0; i < func->parameters().size(); ++i) { - if (i != 0) - code += ", "; - const Parameter &p = func->parameters().at(i); - code += p.dataType(); - if (code[code.size()-1].isLetterOrNumber()) - code += QLatin1Char(' '); - code += p.name(); - } + func->parameters().getTypeAndNameList(code); code += "){ /* ... */ });"; @@ -1678,7 +1685,7 @@ Generator *Generator::generatorForFormat(const QString& format) return *g; ++g; } - return 0; + return nullptr; } /*! @@ -1949,7 +1956,7 @@ void Generator::initializeGenerator(const Config& config) bool Generator::matchAhead(const Atom *atom, Atom::AtomType expectedAtomType) { - return atom->next() != 0 && atom->next()->type() == expectedAtomType; + return atom->next() != nullptr && atom->next()->type() == expectedAtomType; } /*! @@ -2114,7 +2121,7 @@ int Generator::skipAtoms(const Atom *atom, Atom::AtomType type) const { int skipAhead = 0; atom = atom->next(); - while (atom != 0 && atom->type() != type) { + while (atom != nullptr && atom->type() != type) { skipAhead++; atom = atom->next(); } @@ -2139,30 +2146,30 @@ void Generator::initializeTextOutput() void Generator::supplementAlsoList(const Node *node, QList<Text> &alsoList) { if (node->isFunction() && !node->isMacro()) { - const FunctionNode *func = static_cast<const FunctionNode *>(node); - if (func->overloadNumber() == 0) { + const FunctionNode *fn = static_cast<const FunctionNode *>(node); + if (fn->overloadNumber() == 0) { QString alternateName; - const FunctionNode *alternateFunc = 0; + const FunctionNode *alternateFunc = nullptr; - if (func->name().startsWith("set") && func->name().size() >= 4) { - alternateName = func->name()[3].toLower(); - alternateName += func->name().mid(4); - alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + if (fn->name().startsWith("set") && fn->name().size() >= 4) { + alternateName = fn->name()[3].toLower(); + alternateName += fn->name().mid(4); + alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); if (!alternateFunc) { - alternateName = "is" + func->name().mid(3); - alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + alternateName = "is" + fn->name().mid(3); + alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); if (!alternateFunc) { - alternateName = "has" + func->name().mid(3); - alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + alternateName = "has" + fn->name().mid(3); + alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); } } } - else if (!func->name().isEmpty()) { + else if (!fn->name().isEmpty()) { alternateName = "set"; - alternateName += func->name()[0].toUpper(); - alternateName += func->name().mid(1); - alternateFunc = func->parent()->findFunctionNode(alternateName, QString()); + alternateName += fn->name()[0].toUpper(); + alternateName += fn->name().mid(1); + alternateFunc = fn->parent()->findFunctionChild(alternateName, QString()); } if (alternateFunc && alternateFunc->access() != Node::Private) { @@ -2225,35 +2232,47 @@ QString Generator::trimmedTrailing(const QString& string, const QString &prefix, QString Generator::typeString(const Node *node) { - switch (node->type()) { + switch (node->nodeType()) { case Node::Namespace: return "namespace"; case Node::Class: return "class"; + case Node::Struct: + return "struct"; + case Node::Union: + return "union"; case Node::QmlType: return "type"; case Node::QmlBasicType: return "type"; - case Node::Document: + case Node::Page: return "documentation"; case Node::Enum: return "enum"; case Node::Typedef: return "typedef"; - case Node::Function: + case Node::Function: { + const FunctionNode *fn = static_cast<const FunctionNode*>(node); + switch (fn->metaness()) { + case FunctionNode::JsSignal: + case FunctionNode::QmlSignal: + return "signal"; + case FunctionNode::JsSignalHandler: + case FunctionNode::QmlSignalHandler: + return "signal handler"; + case FunctionNode::JsMethod: + case FunctionNode::QmlMethod: + return "method"; + default: + break; + } return "function"; + } case Node::Property: case Node::QmlProperty: return "property"; - case Node::QmlPropertyGroup: - return "property group"; - case Node::QmlSignal: - return "signal"; - case Node::QmlSignalHandler: - return "signal handler"; - case Node::QmlMethod: - return "method"; case Node::Module: + case Node::JsModule: case Node::QmlModule: return "module"; default: |