Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
clangcodeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
6
7#include "access.h"
8#include "classnode.h"
9#include "config.h"
10#include "doc.h"
11#include "enumnode.h"
12#include "functionnode.h"
13#include "genustypes.h"
15#include "namespacenode.h"
16#include "propertynode.h"
17#include "qdocdatabase.h"
18#include "typedefnode.h"
19#include "variablenode.h"
21#include "utilities.h"
22
23#include <QtCore/qdebug.h>
24#include <QtCore/qdir.h>
25#include <QtCore/qelapsedtimer.h>
26#include <QtCore/qfile.h>
27#include <QtCore/qregularexpression.h>
28#include <QtCore/qscopedvaluerollback.h>
29#include <QtCore/qtemporarydir.h>
30#include <QtCore/qtextstream.h>
31#include <QtCore/qvarlengtharray.h>
32
33#include <clang-c/Index.h>
34
35#include <clang/AST/Decl.h>
36#include <clang/AST/DeclFriend.h>
37#include <clang/AST/DeclTemplate.h>
38#include <clang/AST/Expr.h>
39#include <clang/AST/Type.h>
40#include <clang/AST/TypeLoc.h>
41#include <clang/Basic/SourceLocation.h>
42#include <clang/Frontend/ASTUnit.h>
43#include <clang/Lex/Lexer.h>
44#include <llvm/Support/Casting.h>
45
46#include "clang/AST/QualTypeNames.h"
48
49#include <cstdio>
50#include <optional>
51
52QT_BEGIN_NAMESPACE
53
54using namespace Qt::Literals::StringLiterals;
55
57 CXIndex index = nullptr;
58
59 operator CXIndex() {
60 return index;
61 }
62
64 clang_disposeIndex(index);
65 }
66};
67
69 CXTranslationUnit tu = nullptr;
70
71 operator CXTranslationUnit() {
72 return tu;
73 }
74
75 operator bool() {
76 return tu;
77 }
78
80 clang_disposeTranslationUnit(tu);
81 }
82};
83
84// We're printing diagnostics in ClangCodeParser::printDiagnostics,
85// so avoid clang itself printing them.
86static const auto kClangDontDisplayDiagnostics = 0;
87
88static CXTranslationUnit_Flags flags_ = static_cast<CXTranslationUnit_Flags>(0);
89
90constexpr const char fnDummyFileName[] = "/fn_dummyfile.cpp";
91
92#ifndef QT_NO_DEBUG_STREAM
93template<class T>
94static QDebug operator<<(QDebug debug, const std::vector<T> &v)
95{
96 QDebugStateSaver saver(debug);
97 debug.noquote();
98 debug.nospace();
99 const size_t size = v.size();
100 debug << "std::vector<>[" << size << "](";
101 for (size_t i = 0; i < size; ++i) {
102 if (i)
103 debug << ", ";
104 debug << v[i];
105 }
106 debug << ')';
107 return debug;
108}
109#endif // !QT_NO_DEBUG_STREAM
110
111static void printDiagnostics(const CXTranslationUnit &translationUnit)
112{
113 if (!lcQdocClang().isDebugEnabled())
114 return;
115
116 static const auto displayOptions = CXDiagnosticDisplayOptions::CXDiagnostic_DisplaySourceLocation
117 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayColumn
118 | CXDiagnosticDisplayOptions::CXDiagnostic_DisplayOption;
119
120 for (unsigned i = 0, numDiagnostics = clang_getNumDiagnostics(translationUnit); i < numDiagnostics; ++i) {
121 auto diagnostic = clang_getDiagnostic(translationUnit, i);
122 auto formattedDiagnostic = clang_formatDiagnostic(diagnostic, displayOptions);
123 qCDebug(lcQdocClang) << clang_getCString(formattedDiagnostic);
124 clang_disposeString(formattedDiagnostic);
125 clang_disposeDiagnostic(diagnostic);
126 }
127}
128
129/*!
130 * Returns the underlying Decl that \a cursor represents.
131 *
132 * This can be used to drop back down from a LibClang's CXCursor to
133 * the underlying C++ AST that Clang provides.
134 *
135 * It should be used when LibClang does not expose certain
136 * functionalities that are available in the C++ AST.
137 *
138 * The CXCursor should represent a declaration. Usages of this
139 * function on CXCursors that do not represent a declaration may
140 * produce undefined results.
141 */
142static const clang::Decl* get_cursor_declaration(CXCursor cursor) {
143 assert(clang_isDeclaration(clang_getCursorKind(cursor)));
144
145 return static_cast<const clang::Decl*>(cursor.data[0]);
146}
147
148
149/*!
150 * Returns a string representing the name of \a type as if it was
151 * referred to at the end of the translation unit that it was parsed
152 * from.
153 *
154 * For example, given the following code:
155 *
156 * \code
157 * namespace foo {
158 * template<typename T>
159 * struct Bar {
160 * using Baz = const T&;
161 *
162 * void bam(Baz);
163 * };
164 * }
165 * \endcode
166 *
167 * Given a parsed translation unit and an AST node, say \e {decl},
168 * representing the parameter declaration of the first argument of \c {bam},
169 * calling \c{get_fully_qualified_name(decl->getType(), * decl->getASTContext())}
170 * would result in the string \c {foo::Bar<T>::Baz}.
171 *
172 * This should generally be used every time the stringified
173 * representation of a type is acquired as part of parsing with Clang,
174 * so as to ensure a consistent behavior and output.
175 */
176static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext& declaration_context) {
178 type,
179 declaration_context,
180 declaration_context.getPrintingPolicy()
181 );
182}
183
184/*
185 * Cleans up anonymous struct names in type strings by replacing
186 * file-path-based identifiers with clean display names.
187 * Only performs expensive cleaning when anonymous types are detected.
188 */
189static QString cleanAnonymousTypeName(const QString &typeName) {
190 if (!typeName.contains("(unnamed "_L1) && !typeName.contains("(anonymous "_L1)) {
191 return typeName; // Fast path for most cases
192 }
193
194 // Only do expensive cleaning when needed
195 static const QRegularExpression pattern(
196 R"(\‍((unnamed|anonymous) (struct|union|class) at [^)]+\‍))"
197 );
198 QString cleaned = typeName;
199 cleaned.replace(pattern, "(\\1 \\2)"_L1);
200 return cleaned;
201}
202
203/*
204 * Retrieves expression as written in the original source code.
205 *
206 * declaration_context should be the ASTContext of the declaration
207 * from which the expression was extracted from.
208 *
209 * If the expression contains a leading equal sign it will be removed.
210 *
211 * Leading and trailing spaces will be similarly removed from the expression.
212 */
213static std::string get_expression_as_string(const clang::Expr* expression, const clang::ASTContext& declaration_context) {
214 QString default_value = QString::fromStdString(clang::Lexer::getSourceText(
215 clang::CharSourceRange::getTokenRange(expression->getSourceRange()),
216 declaration_context.getSourceManager(),
217 declaration_context.getLangOpts()
218 ).str());
219
220 if (default_value.startsWith("="))
221 default_value.remove(0, 1);
222
223 default_value = default_value.trimmed();
224
225 return default_value.toStdString();
226}
227
228/*
229 * Retrieves the default value of the passed in type template parameter as a string.
230 *
231 * The default value of a type template parameter is always a type,
232 * and its stringified representation will be return as the fully
233 * qualified version of the type.
234 *
235 * If the parameter has no default value the empty string will be returned.
236 */
237static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl* parameter) {
238#if LIBCLANG_VERSION_MAJOR >= 19
239 return (parameter && parameter->hasDefaultArgument()) ?
240 get_fully_qualified_type_name(parameter->getDefaultArgument().getArgument().getAsType(), parameter->getASTContext()) :
241 "";
242#else
243 return (parameter && parameter->hasDefaultArgument()) ?
244 get_fully_qualified_type_name(parameter->getDefaultArgument(), parameter->getASTContext()) :
245 "";
246#endif
247
248}
249
250/*
251 * Retrieves the default value of the passed in non-type template parameter as a string.
252 *
253 * The default value of a non-type template parameter is an expression
254 * and its stringified representation will be return as it was written
255 * in the original code.
256 *
257 * If the parameter as no default value the empty string will be returned.
258 */
259static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl* parameter) {
260#if LIBCLANG_VERSION_MAJOR >= 19
261 return (parameter && parameter->hasDefaultArgument()) ?
262 get_expression_as_string(parameter->getDefaultArgument().getSourceExpression(), parameter->getASTContext()) : "";
263#else
264 return (parameter && parameter->hasDefaultArgument()) ?
265 get_expression_as_string(parameter->getDefaultArgument(), parameter->getASTContext()) : "";
266#endif
267
268}
269
270/*
271 * Retrieves the default value of the passed in template template parameter as a string.
272 *
273 * The default value of a template template parameter is a template
274 * name and its stringified representation will be returned as a fully
275 * qualified version of that name.
276 *
277 * If the parameter as no default value the empty string will be returned.
278 */
279static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl* parameter) {
280 std::string default_value{};
281
282 if (parameter && parameter->hasDefaultArgument()) {
283 const clang::TemplateName template_name = parameter->getDefaultArgument().getArgument().getAsTemplate();
284
285 llvm::raw_string_ostream ss{default_value};
286 template_name.print(ss, parameter->getASTContext().getPrintingPolicy(), clang::TemplateName::Qualified::AsWritten);
287 }
288
289 return default_value;
290}
291
292/*
293 * Retrieves the default value of the passed in function parameter as
294 * a string.
295 *
296 * The default value of a function parameter is an expression and its
297 * stringified representation will be returned as it was written in
298 * the original code.
299 *
300 * If the parameter as no default value or Clang was not able to yet
301 * parse it at this time the empty string will be returned.
302 */
303static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl* parameter) {
304 if (!parameter || !parameter->hasDefaultArg() || parameter->hasUnparsedDefaultArg())
305 return "";
306
307 return get_expression_as_string(
308 parameter->hasUninstantiatedDefaultArg() ? parameter->getUninstantiatedDefaultArg() : parameter->getDefaultArg(),
309 parameter->getASTContext()
310 );
311}
312
313/*
314 * Retrieves the default value of the passed in declaration, based on
315 * its concrete type, as a string.
316 *
317 * If the declaration is a nullptr or the concrete type of the
318 * declaration is not a supported one, the returned string will be the
319 * empty string.
320 */
321static std::string get_default_value_initializer_as_string(const clang::NamedDecl* declaration) {
322 if (!declaration) return "";
323
324 if (auto type_template_parameter = llvm::dyn_cast<clang::TemplateTypeParmDecl>(declaration))
325 return get_default_value_initializer_as_string(type_template_parameter);
326
327 if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(declaration))
328 return get_default_value_initializer_as_string(non_type_template_parameter);
329
330 if (auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(declaration)) {
331 return get_default_value_initializer_as_string(template_template_parameter);
332 }
333
334 if (auto function_parameter = llvm::dyn_cast<clang::ParmVarDecl>(declaration)) {
335 return get_default_value_initializer_as_string(function_parameter);
336 }
337
338 return "";
339}
340
341/*!
342 Call clang_visitChildren on the given cursor with the lambda as a callback
343 T can be any functor that is callable with a CXCursor parameter and returns a CXChildVisitResult
344 (in other word compatible with function<CXChildVisitResult(CXCursor)>
345 */
346template<typename T>
347bool visitChildrenLambda(CXCursor cursor, T &&lambda)
348{
349 CXCursorVisitor visitor = [](CXCursor c, CXCursor,
350 CXClientData client_data) -> CXChildVisitResult {
351 return (*static_cast<T *>(client_data))(c);
352 };
353 return clang_visitChildren(cursor, visitor, &lambda);
354}
355
356/*!
357 convert a CXString to a QString, and dispose the CXString
358 */
359static QString fromCXString(CXString &&string)
360{
361 QString ret = QString::fromUtf8(clang_getCString(string));
362 clang_disposeString(string);
363 return ret;
364}
365
366/*
367 * Returns an intermediate representation that models the the given
368 * template declaration.
369 */
370static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl* template_declaration) {
371 assert(template_declaration);
372
373 RelaxedTemplateDeclaration template_declaration_ir{};
374
375 auto template_parameters = template_declaration->getTemplateParameters();
376 for (auto template_parameter : template_parameters->asArray()) {
377 auto kind{RelaxedTemplateParameter::Kind::TypeTemplateParameter};
378 std::string type{};
379
380 if (auto non_type_template_parameter = llvm::dyn_cast<clang::NonTypeTemplateParmDecl>(template_parameter)) {
381 kind = RelaxedTemplateParameter::Kind::NonTypeTemplateParameter;
382 type = get_fully_qualified_type_name(non_type_template_parameter->getType(), non_type_template_parameter->getASTContext());
383
384 // REMARK: QDoc uses this information to match a user
385 // provided documentation (for example from an "\fn"
386 // command) with a `Node` that was extracted from the
387 // code-base.
388 //
389 // Due to how QDoc obtains an AST for documentation that
390 // is provided by the user, there might be a mismatch in
391 // the type of certain non type template parameters.
392 //
393 // QDoc generally builds a fake out-of-line definition for
394 // a callable provided through an "\fn" command, when it
395 // needs to match it.
396 // In that context, certain type names may be dependent
397 // names, while they may not be when the element they
398 // represent is extracted from the code-base.
399 //
400 // This in turn makes their stringified representation
401 // different in the two contextes, as a dependent name may
402 // require the "typename" keyword to precede it.
403 //
404 // Since QDoc uses a very simplified model, and it
405 // generally doesn't need care about the exact name
406 // resolution rules for C++, since it passes by
407 // Clang-validated data, we remove the "typename" keyword
408 // if it prefixes the type representation, so that it
409 // doesn't impact the matching procedure..
410
411 // KLUDGE: Waiting for C++20 to avoid the conversion.
412 // Doesn't really impact performance in a
413 // meaningful way so it can be kept while waiting.
414 if (QString::fromStdString(type).startsWith("typename ")) type.erase(0, std::string("typename ").size());
415 }
416
417 auto template_template_parameter = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(template_parameter);
418 if (template_template_parameter) kind = RelaxedTemplateParameter::Kind::TemplateTemplateParameter;
419
420 template_declaration_ir.parameters.push_back({
421 kind,
422 template_parameter->isTemplateParameterPack(),
423 {
424 std::move(type),
425 template_parameter->getNameAsString(),
426 get_default_value_initializer_as_string(template_parameter)
427 },
428 (template_template_parameter ?
429 std::optional<TemplateDeclarationStorage>(TemplateDeclarationStorage{
430 get_template_declaration(template_template_parameter).parameters
431 }) : std::nullopt)
432 });
433 }
434
435 if (const clang::Expr* requires_clause = template_parameters->getRequiresClause()) {
436 template_declaration_ir.requires_clause =
437 QString::fromStdString(get_expression_as_string(
438 requires_clause, template_declaration->getASTContext())).simplified().toStdString();
439 }
440
441 return template_declaration_ir;
442}
443
444/*!
445 convert a CXSourceLocation to a qdoc Location
446 */
447static Location fromCXSourceLocation(CXSourceLocation location)
448{
449 unsigned int line, column;
450 CXString file;
451 clang_getPresumedLocation(location, &file, &line, &column);
452 Location l(fromCXString(std::move(file)));
453 l.setColumnNo(column);
454 l.setLineNo(line);
455 return l;
456}
457
458/*!
459 convert a CX_CXXAccessSpecifier to Node::Access
460 */
461static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
462{
463 switch (spec) {
464 case CX_CXXPrivate:
465 return Access::Private;
466 case CX_CXXProtected:
467 return Access::Protected;
468 case CX_CXXPublic:
469 return Access::Public;
470 default:
471 return Access::Public;
472 }
473}
474
475/*!
476 Returns the spelling in the file for a source range
477 */
478
484
485static inline QString fromCache(const QByteArray &cache,
486 unsigned int offset1, unsigned int offset2)
487{
488 return QString::fromUtf8(cache.mid(offset1, offset2 - offset1));
489}
490
491static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
492{
493 using FileCache = QList<FileCacheEntry>;
494 static FileCache cache;
495
496 CXString cxFileName = clang_getFileName(cxFile);
497 const QByteArray fileName = clang_getCString(cxFileName);
498 clang_disposeString(cxFileName);
499
500 for (const auto &entry : std::as_const(cache)) {
501 if (fileName == entry.fileName)
502 return fromCache(entry.content, offset1, offset2);
503 }
504
505 QFile file(QString::fromUtf8(fileName));
506 if (file.open(QIODeviceBase::ReadOnly)) { // binary to match clang offsets
507 FileCacheEntry entry{std::move(fileName), file.readAll()};
508 cache.prepend(entry);
509 while (cache.size() > 5)
510 cache.removeLast();
511 return fromCache(entry.content, offset1, offset2);
512 }
513 return {};
514}
515
516static QString getSpelling(CXSourceRange range)
517{
518 auto start = clang_getRangeStart(range);
519 auto end = clang_getRangeEnd(range);
520 CXFile file1, file2;
521 unsigned int offset1, offset2;
522 clang_getFileLocation(start, &file1, nullptr, nullptr, &offset1);
523 clang_getFileLocation(end, &file2, nullptr, nullptr, &offset2);
524
525 if (file1 != file2 || offset2 <= offset1)
526 return QString();
527
528 return readFile(file1, offset1, offset2);
529}
530
531/*!
532 Returns the function name from a given cursor representing a
533 function declaration. This is usually clang_getCursorSpelling, but
534 not for the conversion function in which case it is a bit more complicated
535 */
536QString functionName(CXCursor cursor)
537{
538 if (clang_getCursorKind(cursor) == CXCursor_ConversionFunction) {
539 // For a CXCursor_ConversionFunction we don't want the spelling which would be something
540 // like "operator type-parameter-0-0" or "operator unsigned int". we want the actual name as
541 // spelled;
542 auto conversion_declaration =
543 static_cast<const clang::CXXConversionDecl*>(get_cursor_declaration(cursor));
544
545 return QLatin1String("operator ") + QString::fromStdString(get_fully_qualified_type_name(
546 conversion_declaration->getConversionType(),
547 conversion_declaration->getASTContext()
548 ));
549 }
550
551 QString name = fromCXString(clang_getCursorSpelling(cursor));
552
553 // Remove template stuff from constructor and destructor but not from operator<
554 auto ltLoc = name.indexOf('<');
555 if (ltLoc > 0 && !name.startsWith("operator<"))
556 name = name.left(ltLoc);
557 return name;
558}
559
560/*!
561 Reconstruct the qualified path name of a function that is
562 being overridden.
563 */
564static QString reconstructQualifiedPathForCursor(CXCursor cur)
565{
566 QString path;
567 auto kind = clang_getCursorKind(cur);
568 while (!clang_isInvalid(kind) && kind != CXCursor_TranslationUnit) {
569 switch (kind) {
570 case CXCursor_Namespace:
571 case CXCursor_StructDecl:
572 case CXCursor_ClassDecl:
573 case CXCursor_UnionDecl:
574 case CXCursor_ClassTemplate:
575 path.prepend("::");
576 path.prepend(fromCXString(clang_getCursorSpelling(cur)));
577 break;
578 case CXCursor_FunctionDecl:
579 case CXCursor_FunctionTemplate:
580 case CXCursor_CXXMethod:
581 case CXCursor_Constructor:
582 case CXCursor_Destructor:
583 case CXCursor_ConversionFunction:
584 path = functionName(cur);
585 break;
586 default:
587 break;
588 }
589 cur = clang_getCursorSemanticParent(cur);
590 kind = clang_getCursorKind(cur);
591 }
592 return path;
593}
594
595/*!
596 \internal
597
598 Extract a class name from a Clang parameter type, stripping references,
599 pointers, and qualifiers. Returns \c {std::nullopt} if the type doesn't
600 represent a class.
601 */
602static std::optional<QString> classNameFromParameterType(clang::QualType param_type)
603{
604 param_type = param_type.getNonReferenceType();
605 while (param_type->isPointerType())
606 param_type = param_type->getPointeeType();
607 param_type = param_type.getUnqualifiedType();
608
609 if (param_type->isBuiltinType())
610 return std::nullopt;
611
612 if (const auto *record_type = param_type->getAs<clang::RecordType>()) {
613 if (const auto *record_decl = record_type->getDecl())
614 return QString::fromStdString(record_decl->getQualifiedNameAsString());
615 }
616
617 // The type may be incomplete (forward-declared or unknown during \fn parsing).
618 // Extract the class name from the type spelling if it looks like a class.
619 QString class_name = QString::fromStdString(param_type.getAsString());
620 class_name.remove("const "_L1).remove("volatile "_L1);
621 class_name.remove("class "_L1).remove("struct "_L1);
622 class_name = class_name.trimmed();
623
624 if (class_name.isEmpty() || class_name.contains('('_L1) || class_name.contains('['_L1))
625 return std::nullopt;
626
627 // Strip template arguments (e.g. "QList<MyClass>" becomes "QList") as
628 // hidden friends are declared in the primary type.
629 if (auto angle = class_name.indexOf('<'_L1); angle > 0)
630 class_name.truncate(angle);
631
632 return class_name;
633}
634
635/*!
636 Find the node from the QDocDatabase \a qdb that corresponds to the declaration
637 represented by the cursor \a cur, if it exists.
638 */
639static Node *findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
640{
641 auto kind = clang_getCursorKind(cur);
642 if (clang_isInvalid(kind))
643 return nullptr;
644 if (kind == CXCursor_TranslationUnit)
645 return qdb->primaryTreeRoot();
646
647 Node *p = findNodeForCursor(qdb, clang_getCursorSemanticParent(cur));
648 // Special case; if the cursor represents a template type|non-type|template parameter
649 // and its semantic parent is a function, return a pointer to the function node.
650 if (p && p->isFunction(Genus::CPP)) {
651 switch (kind) {
652 case CXCursor_TemplateTypeParameter:
653 case CXCursor_NonTypeTemplateParameter:
654 case CXCursor_TemplateTemplateParameter:
655 return p;
656 default:
657 break;
658 }
659 }
660
661 // ...otherwise, the semantic parent must be an Aggregate node.
662 if (!p || !p->isAggregate())
663 return nullptr;
664 auto parent = static_cast<Aggregate *>(p);
665
666 QString name;
667 if (clang_Cursor_isAnonymous(cur)) {
668 name = Utilities::uniqueIdentifier(
669 fromCXSourceLocation(clang_getCursorLocation(cur)),
670 QLatin1String("anonymous"));
671 } else {
672 name = fromCXString(clang_getCursorSpelling(cur));
673 }
674 switch (kind) {
675 case CXCursor_Namespace:
676 return parent->findNonfunctionChild(name, &Node::isNamespace);
677 case CXCursor_StructDecl:
678 case CXCursor_ClassDecl:
679 case CXCursor_UnionDecl:
680 case CXCursor_ClassTemplate:
681 return parent->findNonfunctionChild(name, &Node::isClassNode);
682 case CXCursor_FunctionDecl:
683 case CXCursor_FunctionTemplate:
684 case CXCursor_CXXMethod:
685 case CXCursor_Constructor:
686 case CXCursor_Destructor:
687 case CXCursor_ConversionFunction: {
688 NodeVector candidates;
689 parent->findChildren(functionName(cur), candidates);
690 // Hidden friend functions are recorded under their lexical parent in the database
691 auto *cur_decl = get_cursor_declaration(cur);
692 if (candidates.isEmpty() && cur_decl && cur_decl->getFriendObjectKind() != clang::Decl::FOK_None) {
693 if (auto *lexical_parent = findNodeForCursor(qdb, clang_getCursorLexicalParent(cur));
694 lexical_parent && lexical_parent->isAggregate() && lexical_parent != parent) {
695 static_cast<Aggregate *>(lexical_parent)->findChildren(functionName(cur), candidates);
696 }
697 }
698
699 // Fallback for hidden friends documented with \fn using unqualified syntax.
700 // When a free function like "bool operator==(const MyClass&, const MyClass&)"
701 // isn't found, search classes referenced in the parameters for hidden friends.
702 if (candidates.isEmpty()) {
703 auto *func_decl = cur_decl ? cur_decl->getAsFunction() : nullptr;
704 if (!func_decl)
705 return nullptr;
706
707 QString funcName = functionName(cur);
708 QSet<ClassNode *> searched_classes;
709
710 for (unsigned i = 0; i < func_decl->getNumParams(); ++i) {
711 auto class_name = classNameFromParameterType(func_decl->getParamDecl(i)->getType());
712 if (!class_name)
713 continue;
714
715 auto *class_node = qdb->findClassNode(class_name->split("::"_L1));
716 if (!class_node || searched_classes.contains(class_node))
717 continue;
718
719 searched_classes.insert(class_node);
720 NodeVector class_candidates;
721 class_node->findChildren(funcName, class_candidates);
722
723 for (Node *candidate : class_candidates) {
724 if (!candidate->isFunction(Genus::CPP))
725 continue;
726 auto *fn = static_cast<FunctionNode *>(candidate);
727 if (fn->isHiddenFriend())
728 candidates.append(candidate);
729 }
730 }
731 }
732
733 if (candidates.isEmpty())
734 return nullptr;
735
736 CXType funcType = clang_getCursorType(cur);
737 auto numArg = clang_getNumArgTypes(funcType);
738 bool isVariadic = clang_isFunctionTypeVariadic(funcType);
739 QVarLengthArray<QString, 20> args;
740
741 std::optional<RelaxedTemplateDeclaration> relaxed_template_declaration{std::nullopt};
742 if (kind == CXCursor_FunctionTemplate)
743 relaxed_template_declaration = get_template_declaration(
744 get_cursor_declaration(cur)->getAsFunction()->getDescribedFunctionTemplate()
745 );
746
747 for (Node *candidate : std::as_const(candidates)) {
748 if (!candidate->isFunction(Genus::CPP))
749 continue;
750
751 auto fn = static_cast<FunctionNode *>(candidate);
752
753 if (!fn->templateDecl() && relaxed_template_declaration)
754 continue;
755
756 if (fn->templateDecl() && !relaxed_template_declaration)
757 continue;
758
759 if (fn->templateDecl() && relaxed_template_declaration &&
760 !are_template_declarations_substitutable(*fn->templateDecl(), *relaxed_template_declaration))
761 continue;
762
763 const Parameters &parameters = fn->parameters();
764
765 if (parameters.count() != numArg + isVariadic) {
766 // Ignore possible last argument of type QPrivateSignal as it may have been dropped
767 if (numArg > 0 && parameters.isPrivateSignal() &&
768 (parameters.isEmpty() || !parameters.last().type().endsWith(
769 QLatin1String("QPrivateSignal")))) {
770 if (parameters.count() != --numArg + isVariadic)
771 continue;
772 } else {
773 continue;
774 }
775 }
776
777 if (fn->isConst() != bool(clang_CXXMethod_isConst(cur)))
778 continue;
779
780 if (isVariadic && parameters.last().type() != QLatin1String("..."))
781 continue;
782
783 if (fn->isRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_LValue))
784 continue;
785
786 if (fn->isRefRef() != (clang_Type_getCXXRefQualifier(funcType) == CXRefQualifier_RValue))
787 continue;
788
789 auto function_declaration = get_cursor_declaration(cur)->getAsFunction();
790
791 bool different = false;
792 for (int i = 0; i < numArg; ++i) {
793 CXType argType = clang_getArgType(funcType, i);
794
795 if (args.size() <= i)
796 args.append(QString::fromStdString(get_fully_qualified_type_name(
797 function_declaration->getParamDecl(i)->getOriginalType(),
798 function_declaration->getASTContext()
799 )));
800
801 QString recordedType = parameters.at(i).type();
802 QString typeSpelling = args.at(i);
803
804 different = recordedType != typeSpelling;
805
806 // Retry with a canonical type spelling
807 if (different && (argType.kind == CXType_Typedef || argType.kind == CXType_Elaborated)) {
808 QStringView canonicalType = parameters.at(i).canonicalType();
809 if (!canonicalType.isEmpty()) {
810 different = canonicalType !=
811 QString::fromStdString(get_fully_qualified_type_name(
812 function_declaration->getParamDecl(i)->getOriginalType().getCanonicalType(),
813 function_declaration->getASTContext()
814 ));
815 }
816 }
817
818 if (different) {
819 break;
820 }
821 }
822
823 if (!different)
824 return fn;
825 }
826 return nullptr;
827 }
828 case CXCursor_EnumDecl:
829 return parent->findNonfunctionChild(name, &Node::isEnumType);
830 case CXCursor_FieldDecl:
831 case CXCursor_VarDecl:
832 return parent->findNonfunctionChild(name, &Node::isVariable);
833 case CXCursor_TypedefDecl:
834 return parent->findNonfunctionChild(name, &Node::isTypedef);
835 default:
836 return nullptr;
837 }
838}
839
840static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
841{
842 CXCursor *overridden;
843 unsigned int numOverridden = 0;
844 clang_getOverriddenCursors(cursor, &overridden, &numOverridden);
845 for (uint i = 0; i < numOverridden; ++i) {
846 QString path = reconstructQualifiedPathForCursor(overridden[i]);
847 if (!path.isEmpty()) {
848 fn->setOverride(true);
849 fn->setOverridesThis(path);
850 break;
851 }
852 }
853 clang_disposeOverriddenCursors(overridden);
854}
855
856/*!
857 \internal
858 Auto-generates documentation for explicitly defaulted or deleted
859 special member functions that don't already have documentation.
860*/
861static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
862{
863 if (fn->hasDoc())
864 return;
866 return;
868 return;
869
870 QString docSource;
871 if (fn->isDtor()) {
872 docSource = u"Destroys the instance of \\notranslate %1."_s.arg(className);
873 if (fn->isVirtual())
874 docSource += u" This destructor is virtual."_s;
875 } else if (fn->isCtor()) {
876 docSource = u"Default-constructs an instance of \\notranslate %1."_s.arg(className);
877 } else if (fn->isCCtor()) {
878 docSource = u"Copy-constructs an instance of \\notranslate %1."_s.arg(className);
879 } else if (fn->isMCtor()) {
880 docSource = u"Move-constructs an instance of \\notranslate %1."_s.arg(className);
881 } else if (fn->isCAssign() || fn->isMAssign()) {
882 const auto &params = fn->parameters();
883 const QString other = (!params.isEmpty() && !params.at(0).name().isEmpty())
884 ? params.at(0).name()
885 : u"other"_s;
886 docSource = fn->isCAssign()
887 ? u"Copy-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className)
888 : u"Move-assigns \\a %1 to this \\notranslate %2 instance."_s.arg(other, className);
889 }
890
891 if (docSource.isEmpty())
892 return;
893
894 if (fn->isDeletedAsWritten())
895 docSource += u" This function is deleted."_s;
896
897 static const QSet<QString> noMetaCommands;
898 static const QSet<QString> noTopics;
899 Doc doc(fn->location(), fn->location(), docSource, noMetaCommands, noTopics);
901 fn->setDoc(doc);
902}
903
905{
906public:
907 ClangVisitor(QDocDatabase *qdb, const std::set<Config::HeaderFilePath> &allHeaders,
908 const Config::InternalFilePatterns& internalFilePatterns)
909 : qdb_(qdb), parent_(qdb->primaryTreeRoot()),
910 internalFilePatterns_(internalFilePatterns)
911 {
912 std::transform(allHeaders.cbegin(), allHeaders.cend(), std::inserter(allHeaders_, allHeaders_.begin()),
913 [](const auto& header_file_path) -> const QString& { return header_file_path.filename; });
914 }
915
916 QDocDatabase *qdocDB() { return qdb_; }
917
918 CXChildVisitResult visitChildren(CXCursor cursor)
919 {
920 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
921 auto loc = clang_getCursorLocation(cur);
922 if (clang_Location_isFromMainFile(loc))
923 return visitSource(cur, loc);
924
925 CXFile file;
926 clang_getFileLocation(loc, &file, nullptr, nullptr, nullptr);
927 bool isInteresting = false;
928 auto it = isInterestingCache_.find(file);
929 if (it != isInterestingCache_.end()) {
930 isInteresting = *it;
931 } else {
932 QFileInfo fi(fromCXString(clang_getFileName(file)));
933 // Match by file name in case of PCH/installed headers
934 isInteresting = allHeaders_.find(fi.fileName()) != allHeaders_.end();
935 isInterestingCache_[file] = isInteresting;
936 }
937 if (isInteresting) {
938 return visitHeader(cur, loc);
939 }
940
941 return CXChildVisit_Continue;
942 });
943 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
944 }
945
946 /*
947 Not sure about all the possibilities, when the cursor
948 location is not in the main file.
949 */
950 CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
951 {
952 auto ret = visitChildrenLambda(cursor, [&](CXCursor cur) {
953 auto loc = clang_getCursorLocation(cur);
954 if (clang_Location_isFromMainFile(loc))
955 return visitFnSignature(cur, loc, fnNode, ignoreSignature);
956 return CXChildVisit_Continue;
957 });
958 return ret ? CXChildVisit_Break : CXChildVisit_Continue;
959 }
960
961 Node *nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc);
962
963private:
964 bool detectQmlSingleton(CXCursor cursor);
965 /*!
966 SimpleLoc represents a simple location in the main source file,
967 which can be used as a key in a QMap.
968 */
969 struct SimpleLoc
970 {
971 unsigned int line {}, column {};
972 friend bool operator<(const SimpleLoc &a, const SimpleLoc &b)
973 {
974 return a.line != b.line ? a.line < b.line : a.column < b.column;
975 }
976 };
977 /*!
978 \variable ClangVisitor::declMap_
979 Map of all the declarations in the source file so we can match them
980 with a documentation comment.
981 */
982 QMap<SimpleLoc, CXCursor> declMap_;
983
984 QDocDatabase *qdb_;
985 Aggregate *parent_;
986 std::set<QString> allHeaders_;
987 QHash<CXFile, bool> isInterestingCache_; // doing a canonicalFilePath is slow, so keep a cache.
988 const Config::InternalFilePatterns& internalFilePatterns_;
989
990 /*!
991 Returns true if the symbol should be ignored for the documentation.
992 */
993 bool ignoredSymbol(const QString &symbolName)
994 {
995 if (symbolName == QLatin1String("QPrivateSignal"))
996 return true;
997 // Ignore functions generated by property macros
998 if (symbolName.startsWith("_qt_property_"))
999 return true;
1000 // Ignore template argument deduction guides
1001 if (symbolName.startsWith("<deduction guide"))
1002 return true;
1003 return false;
1004 }
1005
1006 CXChildVisitResult visitSource(CXCursor cursor, CXSourceLocation loc);
1007 CXChildVisitResult visitHeader(CXCursor cursor, CXSourceLocation loc);
1008 CXChildVisitResult visitFnSignature(CXCursor cursor, CXSourceLocation loc, Node **fnNode,
1009 bool &ignoreSignature);
1010 void processFunction(FunctionNode *fn, CXCursor cursor);
1011 bool parseProperty(const QString &spelling, const Location &loc);
1012 void readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor);
1013 Aggregate *getSemanticParent(CXCursor cursor);
1014};
1015
1016/*!
1017 Detects if a class cursor contains the \e QML_SINGLETON macro.
1018 Returns true if the macro is detected, false otherwise.
1019
1020 The \e QML_SINGLETON macro expands to multiple items including:
1021 \list
1022 \li \c {Q_CLASSINFO("QML.Singleton", "true")}
1023 \li \c {enum class QmlIsSingleton}
1024 \endlist
1025
1026 This method looks for these expansion artifacts to detect the macro.
1027*/
1028bool ClangVisitor::detectQmlSingleton(CXCursor cursor)
1029{
1030 bool hasSingletonMacro = false;
1031
1032 visitChildrenLambda(cursor, [&hasSingletonMacro](CXCursor child) -> CXChildVisitResult {
1033 // Look for Q_CLASSINFO calls that indicate QML.Singleton
1034 if (clang_getCursorKind(child) == CXCursor_CallExpr) {
1035 CXSourceRange range = clang_getCursorExtent(child);
1036 QString sourceText = getSpelling(range);
1037 // More precise matching: look for the exact Q_CLASSINFO pattern
1038 static const QRegularExpression qmlSingletonPattern(
1039 R"(Q_CLASSINFO\s*\‍(\s*["\']QML\.Singleton["\']\s*,\s*["\']true["\']\s*\‍))");
1040 if (qmlSingletonPattern.match(sourceText).hasMatch()) {
1041 hasSingletonMacro = true;
1042 return CXChildVisit_Break;
1043 }
1044 }
1045
1046 // Also check for enum class QmlIsSingleton which is part of the macro expansion
1047 if (clang_getCursorKind(child) == CXCursor_EnumDecl) {
1048 QString spelling = fromCXString(clang_getCursorSpelling(child));
1049 if (spelling == "QmlIsSingleton"_L1) {
1050 hasSingletonMacro = true;
1051 return CXChildVisit_Break;
1052 }
1053 }
1054
1055 return CXChildVisit_Continue;
1056 });
1057
1058 return hasSingletonMacro;
1059}
1060
1061/*!
1062 Visits a cursor in the .cpp file.
1063 This fills the declMap_
1064 */
1065CXChildVisitResult ClangVisitor::visitSource(CXCursor cursor, CXSourceLocation loc)
1066{
1067 auto kind = clang_getCursorKind(cursor);
1068 if (clang_isDeclaration(kind)) {
1069 SimpleLoc l;
1070 clang_getPresumedLocation(loc, nullptr, &l.line, &l.column);
1071 declMap_.insert(l, cursor);
1072 return CXChildVisit_Recurse;
1073 }
1074 return CXChildVisit_Continue;
1075}
1076
1077/*!
1078 If the semantic and lexical parent cursors of \a cursor are
1079 not the same, find the Aggregate node for the semantic parent
1080 cursor and return it. Otherwise return the current parent.
1081 */
1082Aggregate *ClangVisitor::getSemanticParent(CXCursor cursor)
1083{
1084 CXCursor sp = clang_getCursorSemanticParent(cursor);
1085 CXCursor lp = clang_getCursorLexicalParent(cursor);
1086 if (!clang_equalCursors(sp, lp) && clang_isDeclaration(clang_getCursorKind(sp))) {
1087 Node *spn = findNodeForCursor(qdb_, sp);
1088 if (spn && spn->isAggregate()) {
1089 return static_cast<Aggregate *>(spn);
1090 }
1091 }
1092 return parent_;
1093}
1094
1095CXChildVisitResult ClangVisitor::visitFnSignature(CXCursor cursor, CXSourceLocation, Node **fnNode,
1096 bool &ignoreSignature)
1097{
1098 switch (clang_getCursorKind(cursor)) {
1099 case CXCursor_Namespace:
1100 return CXChildVisit_Recurse;
1101 case CXCursor_FunctionDecl:
1102 case CXCursor_FunctionTemplate:
1103 case CXCursor_CXXMethod:
1104 case CXCursor_Constructor:
1105 case CXCursor_Destructor:
1106 case CXCursor_ConversionFunction: {
1107 ignoreSignature = false;
1108 if (ignoredSymbol(functionName(cursor))) {
1109 *fnNode = nullptr;
1110 ignoreSignature = true;
1111 } else {
1112 *fnNode = findNodeForCursor(qdb_, cursor);
1113 if (*fnNode) {
1114 if ((*fnNode)->isFunction(Genus::CPP)) {
1115 auto *fn = static_cast<FunctionNode *>(*fnNode);
1116 readParameterNamesAndAttributes(fn, cursor);
1117
1118 const clang::Decl* declaration = get_cursor_declaration(cursor);
1119 assert(declaration);
1120 if (const auto function_declaration = declaration->getAsFunction()) {
1121 auto declaredReturnType = function_declaration->getDeclaredReturnType();
1122 if (llvm::dyn_cast_if_present<clang::AutoType>(declaredReturnType.getTypePtrOrNull()))
1123 fn->setDeclaredReturnType(QString::fromStdString(declaredReturnType.getAsString()));
1124 }
1125 }
1126 } else { // Possibly an implicitly generated special member
1127 QString name = functionName(cursor);
1128 if (ignoredSymbol(name))
1129 return CXChildVisit_Continue;
1130 Aggregate *semanticParent = getSemanticParent(cursor);
1131 if (semanticParent && semanticParent->isClass()) {
1132 auto *candidate = new FunctionNode(nullptr, name);
1133 processFunction(candidate, cursor);
1134 if (!candidate->isSpecialMemberFunction()) {
1135 delete candidate;
1136 return CXChildVisit_Continue;
1137 }
1138 candidate->setImplicitlyGenerated(true);
1139 semanticParent->addChild(*fnNode = candidate);
1140 }
1141 }
1142 }
1143 break;
1144 }
1145 default:
1146 break;
1147 }
1148 return CXChildVisit_Continue;
1149}
1150
1151CXChildVisitResult ClangVisitor::visitHeader(CXCursor cursor, CXSourceLocation loc)
1152{
1153 auto kind = clang_getCursorKind(cursor);
1154
1155 switch (kind) {
1156 case CXCursor_TypeAliasTemplateDecl:
1157 case CXCursor_TypeAliasDecl: {
1158 const QString aliasName = fromCXString(clang_getCursorSpelling(cursor));
1159 QString aliasedType;
1160
1161 const auto *templateDecl = (kind == CXCursor_TypeAliasTemplateDecl)
1162 ? llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor))
1163 : nullptr;
1164
1165 if (kind == CXCursor_TypeAliasTemplateDecl) {
1166 // For template aliases, get the underlying TypeAliasDecl from the TemplateDecl
1167 if (const auto *aliasTemplate = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
1168 if (const auto *aliasDecl = aliasTemplate->getTemplatedDecl()) {
1169 clang::QualType underlyingType = aliasDecl->getUnderlyingType();
1170 aliasedType = QString::fromStdString(underlyingType.getAsString());
1171 }
1172 }
1173 } else {
1174 // For non-template aliases, get the underlying type via C API
1175 const CXType aliasedCXType = clang_getTypedefDeclUnderlyingType(cursor);
1176 if (aliasedCXType.kind != CXType_Invalid) {
1177 aliasedType = fromCXString(clang_getTypeSpelling(aliasedCXType));
1178 }
1179 }
1180
1181 if (!aliasedType.isEmpty()) {
1182 auto *ta = new TypeAliasNode(parent_, aliasName, aliasedType);
1183 ta->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1184 ta->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1185
1186 if (templateDecl)
1187 ta->setTemplateDecl(get_template_declaration(templateDecl));
1188 }
1189 return CXChildVisit_Continue;
1190 }
1191 case CXCursor_StructDecl:
1192 case CXCursor_UnionDecl:
1193 if (fromCXString(clang_getCursorSpelling(cursor)).isEmpty()) // anonymous struct or union
1194 return CXChildVisit_Continue;
1195 Q_FALLTHROUGH();
1196 case CXCursor_ClassTemplate:
1197 Q_FALLTHROUGH();
1198 case CXCursor_ClassDecl: {
1199 if (!clang_isCursorDefinition(cursor))
1200 return CXChildVisit_Continue;
1201
1202 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1203 return CXChildVisit_Continue;
1204
1205 QString className = cleanAnonymousTypeName(fromCXString(clang_getCursorSpelling(cursor)));
1206
1207 Aggregate *semanticParent = getSemanticParent(cursor);
1208 if (semanticParent && semanticParent->findNonfunctionChild(className, &Node::isClassNode)) {
1209 return CXChildVisit_Continue;
1210 }
1211
1212 CXCursorKind actualKind = (kind == CXCursor_ClassTemplate) ?
1213 clang_getTemplateCursorKind(cursor) : kind;
1214
1216 if (actualKind == CXCursor_StructDecl)
1217 type = NodeType::Struct;
1218 else if (actualKind == CXCursor_UnionDecl)
1219 type = NodeType::Union;
1220
1221 auto *classe = new ClassNode(type, semanticParent, className);
1222 classe->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1223
1224 auto location = fromCXSourceLocation(clang_getCursorLocation(cursor));
1225 classe->setLocation(location);
1226
1227 if (!internalFilePatterns_.exactMatches.isEmpty() || !internalFilePatterns_.globPatterns.isEmpty()
1228 || !internalFilePatterns_.regexPatterns.isEmpty()) {
1229 if (Config::matchesInternalFilePattern(location.filePath(), internalFilePatterns_))
1230 classe->setStatus(Status::Internal);
1231 }
1232
1233 classe->setAnonymous(clang_Cursor_isAnonymous(cursor));
1234
1235 if (detectQmlSingleton(cursor)) {
1236 classe->setQmlSingleton(true);
1237 }
1238
1239 if (kind == CXCursor_ClassTemplate) {
1240 auto template_declaration = llvm::dyn_cast<clang::TemplateDecl>(get_cursor_declaration(cursor));
1241 classe->setTemplateDecl(get_template_declaration(template_declaration));
1242 }
1243
1244 QScopedValueRollback<Aggregate *> setParent(parent_, classe);
1245 return visitChildren(cursor);
1246 }
1247 case CXCursor_CXXBaseSpecifier: {
1248 if (!parent_->isClassNode())
1249 return CXChildVisit_Continue;
1250 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1251 auto type = clang_getCursorType(cursor);
1252 auto baseCursor = clang_getTypeDeclaration(type);
1253 auto baseNode = findNodeForCursor(qdb_, baseCursor);
1254 auto classe = static_cast<ClassNode *>(parent_);
1255 if (baseNode == nullptr || !baseNode->isClassNode()) {
1256 QString bcName = reconstructQualifiedPathForCursor(baseCursor);
1257 classe->addUnresolvedBaseClass(access,
1258 bcName.split(QLatin1String("::"), Qt::SkipEmptyParts));
1259 return CXChildVisit_Continue;
1260 }
1261 auto baseClasse = static_cast<ClassNode *>(baseNode);
1262 classe->addResolvedBaseClass(access, baseClasse);
1263 return CXChildVisit_Continue;
1264 }
1265 case CXCursor_Namespace: {
1266 QString namespaceName = fromCXString(clang_getCursorDisplayName(cursor));
1267 NamespaceNode *ns = nullptr;
1268 if (parent_)
1269 ns = static_cast<NamespaceNode *>(
1270 parent_->findNonfunctionChild(namespaceName, &Node::isNamespace));
1271 if (!ns) {
1272 ns = new NamespaceNode(parent_, namespaceName);
1274 ns->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1275 }
1276 QScopedValueRollback<Aggregate *> setParent(parent_, ns);
1277 return visitChildren(cursor);
1278 }
1279 case CXCursor_FunctionTemplate:
1280 Q_FALLTHROUGH();
1281 case CXCursor_FunctionDecl:
1282 case CXCursor_CXXMethod:
1283 case CXCursor_Constructor:
1284 case CXCursor_Destructor:
1285 case CXCursor_ConversionFunction: {
1286 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1287 return CXChildVisit_Continue;
1288 QString name = functionName(cursor);
1289 if (ignoredSymbol(name))
1290 return CXChildVisit_Continue;
1291 // constexpr constructors generate also a global instance; ignore
1292 if (kind == CXCursor_Constructor && parent_ == qdb_->primaryTreeRoot())
1293 return CXChildVisit_Continue;
1294
1295 auto *fn = new FunctionNode(parent_, name);
1296 CXSourceRange range = clang_Cursor_getCommentRange(cursor);
1297 if (!clang_Range_isNull(range)) {
1298 QString comment = getSpelling(range);
1299 if (comment.startsWith("//!")) {
1300 qsizetype tag = comment.indexOf(QChar('['));
1301 if (tag > 0) {
1302 qsizetype end = comment.indexOf(QChar(']'), ++tag);
1303 if (end > 0)
1304 fn->setTag(comment.mid(tag, end - tag));
1305 }
1306 }
1307 }
1308
1309 processFunction(fn, cursor);
1310
1311 if (kind == CXCursor_FunctionTemplate) {
1312 auto template_declaration = get_cursor_declaration(cursor)->getAsFunction()->getDescribedFunctionTemplate();
1313 fn->setTemplateDecl(get_template_declaration(template_declaration));
1314 }
1315
1316 if (!clang_Location_isInSystemHeader(loc))
1317 autoGenerateSmfDoc(fn, parent_->name());
1318
1319 return CXChildVisit_Continue;
1320 }
1321#if CINDEX_VERSION >= 36
1322 case CXCursor_FriendDecl: {
1323 return visitChildren(cursor);
1324 }
1325#endif
1326 case CXCursor_EnumDecl: {
1327 auto *en = static_cast<EnumNode *>(findNodeForCursor(qdb_, cursor));
1328 if (en && en->items().size())
1329 return CXChildVisit_Continue; // Was already parsed, probably in another TU
1330
1331 QString enumTypeName = fromCXString(clang_getCursorSpelling(cursor));
1332
1333 if (clang_Cursor_isAnonymous(cursor)) {
1334 enumTypeName = "anonymous";
1335 // Generate a unique name to enable auto-tying doc comments in headers
1336 // to anonymous enum declarations
1337 if (Config::instance().get(CONFIG_DOCUMENTATIONINHEADERS).asBool())
1338 enumTypeName = Utilities::uniqueIdentifier(fromCXSourceLocation(clang_getCursorLocation(cursor)), enumTypeName);
1339 if (parent_ && (parent_->isClassNode() || parent_->isNamespace())) {
1340 Node *n = parent_->findNonfunctionChild(enumTypeName, &Node::isEnumType);
1341 if (n)
1342 en = static_cast<EnumNode *>(n);
1343 }
1344 }
1345 if (!en) {
1346 en = new EnumNode(parent_, enumTypeName, clang_EnumDecl_isScoped(cursor));
1347 en->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1348 en->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1349 en->setAnonymous(clang_Cursor_isAnonymous(cursor));
1350 }
1351
1352 // Enum values
1353 visitChildrenLambda(cursor, [&](CXCursor cur) {
1354 if (clang_getCursorKind(cur) != CXCursor_EnumConstantDecl)
1355 return CXChildVisit_Continue;
1356
1357 QString value;
1358 visitChildrenLambda(cur, [&](CXCursor cur) {
1359 if (clang_isExpression(clang_getCursorKind(cur))) {
1360 value = getSpelling(clang_getCursorExtent(cur));
1361 return CXChildVisit_Break;
1362 }
1363 return CXChildVisit_Continue;
1364 });
1365 if (value.isEmpty()) {
1366 QLatin1String hex("0x");
1367 if (!en->items().isEmpty() && en->items().last().value().startsWith(hex)) {
1368 value = hex + QString::number(clang_getEnumConstantDeclValue(cur), 16);
1369 } else {
1370 value = QString::number(clang_getEnumConstantDeclValue(cur));
1371 }
1372 }
1373
1374 en->addItem(EnumItem(fromCXString(clang_getCursorSpelling(cur)), std::move(value)));
1375 return CXChildVisit_Continue;
1376 });
1377 return CXChildVisit_Continue;
1378 }
1379 case CXCursor_FieldDecl:
1380 case CXCursor_VarDecl: {
1381 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1382 return CXChildVisit_Continue;
1383
1384 auto value_declaration =
1385 llvm::dyn_cast<clang::ValueDecl>(get_cursor_declaration(cursor));
1386 assert(value_declaration);
1387
1388 auto access = fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor));
1389 auto var = new VariableNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1390
1391 var->setAccess(access);
1392 var->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1393 var->setLeftType(cleanAnonymousTypeName(QString::fromStdString(get_fully_qualified_type_name(
1394 value_declaration->getType(),
1395 value_declaration->getASTContext()
1396 ))));
1397 var->setStatic(kind == CXCursor_VarDecl && parent_->isClassNode());
1398
1399 return CXChildVisit_Continue;
1400 }
1401 case CXCursor_TypedefDecl: {
1402 if (findNodeForCursor(qdb_, cursor)) // Was already parsed, probably in another TU
1403 return CXChildVisit_Continue;
1404 auto *td = new TypedefNode(parent_, fromCXString(clang_getCursorSpelling(cursor)));
1405 td->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1406 td->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1407 // Search to see if this is a Q_DECLARE_FLAGS (if the type is QFlags<ENUM>)
1408 visitChildrenLambda(cursor, [&](CXCursor cur) {
1409 if (clang_getCursorKind(cur) != CXCursor_TemplateRef
1410 || fromCXString(clang_getCursorSpelling(cur)) != QLatin1String("QFlags"))
1411 return CXChildVisit_Continue;
1412 // Found QFlags<XXX>
1413 visitChildrenLambda(cursor, [&](CXCursor cur) {
1414 if (clang_getCursorKind(cur) != CXCursor_TypeRef)
1415 return CXChildVisit_Continue;
1416 auto *en =
1417 findNodeForCursor(qdb_, clang_getTypeDeclaration(clang_getCursorType(cur)));
1418 if (en && en->isEnumType())
1419 static_cast<EnumNode *>(en)->setFlagsType(td);
1420 return CXChildVisit_Break;
1421 });
1422 return CXChildVisit_Break;
1423 });
1424 return CXChildVisit_Continue;
1425 }
1426 default:
1427 if (clang_isDeclaration(kind) && parent_->isClassNode()) {
1428 // may be a property macro or a static_assert
1429 // which is not exposed from the clang API
1430 parseProperty(getSpelling(clang_getCursorExtent(cursor)),
1432 }
1433 return CXChildVisit_Continue;
1434 }
1435}
1436
1437void ClangVisitor::readParameterNamesAndAttributes(FunctionNode *fn, CXCursor cursor)
1438{
1439 Parameters &parameters = fn->parameters();
1440 // Visit the parameters and attributes
1441 int i = 0;
1442 visitChildrenLambda(cursor, [&](CXCursor cur) {
1443 auto kind = clang_getCursorKind(cur);
1444 if (kind == CXCursor_AnnotateAttr) {
1445 QString annotation = fromCXString(clang_getCursorDisplayName(cur));
1446 if (annotation == QLatin1String("qt_slot")) {
1448 } else if (annotation == QLatin1String("qt_signal")) {
1450 }
1451 if (annotation == QLatin1String("qt_invokable"))
1452 fn->setInvokable(true);
1453 } else if (kind == CXCursor_CXXOverrideAttr) {
1454 fn->setOverride(true);
1455 } else if (kind == CXCursor_ParmDecl) {
1456 if (i >= parameters.count())
1457 return CXChildVisit_Break; // Attributes comes before parameters so we can break.
1458
1459 if (QString name = fromCXString(clang_getCursorSpelling(cur)); !name.isEmpty())
1460 parameters[i].setName(name);
1461
1462 const clang::ParmVarDecl* parameter_declaration = llvm::dyn_cast<const clang::ParmVarDecl>(get_cursor_declaration(cur));
1463 Q_ASSERT(parameter_declaration);
1464
1465 std::string default_value = get_default_value_initializer_as_string(parameter_declaration);
1466
1467 if (!default_value.empty())
1468 parameters[i].setDefaultValue(QString::fromStdString(default_value));
1469
1470 ++i;
1471 }
1472 return CXChildVisit_Continue;
1473 });
1474}
1475
1476void ClangVisitor::processFunction(FunctionNode *fn, CXCursor cursor)
1477{
1478 CXCursorKind kind = clang_getCursorKind(cursor);
1479 CXType funcType = clang_getCursorType(cursor);
1480 fn->setAccess(fromCX_CXXAccessSpecifier(clang_getCXXAccessSpecifier(cursor)));
1481 fn->setLocation(fromCXSourceLocation(clang_getCursorLocation(cursor)));
1482 fn->setStatic(clang_CXXMethod_isStatic(cursor));
1483 fn->setConst(clang_CXXMethod_isConst(cursor));
1484 fn->setVirtualness(!clang_CXXMethod_isVirtual(cursor)
1486 : clang_CXXMethod_isPureVirtual(cursor)
1489
1490 // REMARK: We assume that the following operations and casts are
1491 // generally safe.
1492 // Callers of those methods will generally check at the LibClang
1493 // level the kind of cursor we are dealing with and will pass on
1494 // only valid cursors that are of a function kind and that are at
1495 // least a declaration.
1496 //
1497 // Failure to do so implies a bug in the call chain and should be
1498 // dealt with as such.
1499 const clang::Decl* declaration = get_cursor_declaration(cursor);
1500
1501 assert(declaration);
1502
1503 const clang::FunctionDecl* function_declaration = declaration->getAsFunction();
1504
1505 if (kind == CXCursor_Constructor
1506 // a constructor template is classified as CXCursor_FunctionTemplate
1507 || (kind == CXCursor_FunctionTemplate && fn->name() == parent_->name()))
1509 else if (kind == CXCursor_Destructor)
1511 else if (kind != CXCursor_ConversionFunction)
1512 fn->setReturnType(cleanAnonymousTypeName(QString::fromStdString(get_fully_qualified_type_name(
1513 function_declaration->getReturnType(),
1514 function_declaration->getASTContext()
1515 ))));
1516
1517 const clang::CXXConstructorDecl* constructor_declaration = llvm::dyn_cast<const clang::CXXConstructorDecl>(function_declaration);
1518
1519 if (constructor_declaration && constructor_declaration->isCopyConstructor()) fn->setMetaness(FunctionNode::CCtor);
1520 else if (constructor_declaration && constructor_declaration->isMoveConstructor()) fn->setMetaness(FunctionNode::MCtor);
1521
1522 const clang::CXXConversionDecl* conversion_declaration = llvm::dyn_cast<const clang::CXXConversionDecl>(function_declaration);
1523
1524 if (function_declaration->isConstexpr()) fn->markConstexpr();
1525 if (function_declaration->isExplicitlyDefaulted()) fn->markExplicitlyDefaulted();
1526 if (function_declaration->isDeletedAsWritten()) fn->markDeletedAsWritten();
1527 if (
1528 (constructor_declaration && constructor_declaration->isExplicit()) ||
1529 (conversion_declaration && conversion_declaration->isExplicit())
1530 ) fn->markExplicit();
1531
1532 const clang::CXXMethodDecl* method_declaration = llvm::dyn_cast<const clang::CXXMethodDecl>(function_declaration);
1533
1534 if (method_declaration && method_declaration->isCopyAssignmentOperator()) fn->setMetaness(FunctionNode::CAssign);
1535 else if (method_declaration && method_declaration->isMoveAssignmentOperator()) fn->setMetaness(FunctionNode::MAssign);
1536
1537 const clang::FunctionType* function_type = function_declaration->getFunctionType();
1538 const clang::FunctionProtoType* function_prototype = static_cast<const clang::FunctionProtoType*>(function_type);
1539
1540 if (function_prototype) {
1541 clang::FunctionProtoType::ExceptionSpecInfo exception_specification = function_prototype->getExceptionSpecInfo();
1542
1543 if (exception_specification.Type != clang::ExceptionSpecificationType::EST_None) {
1544 const std::string exception_specification_spelling =
1545 exception_specification.NoexceptExpr ? get_expression_as_string(
1546 exception_specification.NoexceptExpr,
1547 function_declaration->getASTContext()
1548 ) : "";
1549
1550 if (exception_specification_spelling != "false")
1551 fn->markNoexcept(QString::fromStdString(exception_specification_spelling));
1552 }
1553 }
1554
1555 // Extract trailing requires clause.
1556 // From Clang 21 we get an AssociatedConstraint struct (upstream commit 49fd0bf35d2e).
1557 // Earlier Clang versions return Expr*.
1558#if LIBCLANG_VERSION_MAJOR >= 21
1559 if (const auto trailing_requires = function_declaration->getTrailingRequiresClause();
1560 trailing_requires.ConstraintExpr) {
1561 QString requires_str = QString::fromStdString(
1562 get_expression_as_string(trailing_requires.ConstraintExpr,
1563 function_declaration->getASTContext()));
1564 fn->setTrailingRequiresClause(requires_str.simplified());
1565 }
1566#else
1567 if (const clang::Expr *trailing_requires = function_declaration->getTrailingRequiresClause()) {
1568 QString requires_str = QString::fromStdString(
1569 get_expression_as_string(trailing_requires,
1570 function_declaration->getASTContext()));
1571 fn->setTrailingRequiresClause(requires_str.simplified());
1572 }
1573#endif
1574
1575 CXRefQualifierKind refQualKind = clang_Type_getCXXRefQualifier(funcType);
1576 if (refQualKind == CXRefQualifier_LValue)
1577 fn->setRef(true);
1578 else if (refQualKind == CXRefQualifier_RValue)
1579 fn->setRefRef(true);
1580 // For virtual functions, determine what it overrides
1581 // (except for destructor for which we do not want to classify as overridden)
1582 if (!fn->isNonvirtual() && kind != CXCursor_Destructor)
1584
1585 Parameters &parameters = fn->parameters();
1586 parameters.clear();
1587 parameters.reserve(function_declaration->getNumParams());
1588
1589 for (clang::ParmVarDecl* const parameter_declaration : function_declaration->parameters()) {
1590 clang::QualType parameter_type = parameter_declaration->getOriginalType();
1591
1592 parameters.append(cleanAnonymousTypeName(QString::fromStdString(get_fully_qualified_type_name(
1593 parameter_type,
1594 parameter_declaration->getASTContext()
1595 ))));
1596
1597 if (!parameter_type.isCanonical())
1598 parameters.last().setCanonicalType(cleanAnonymousTypeName(QString::fromStdString(get_fully_qualified_type_name(
1599 parameter_type.getCanonicalType(),
1600 parameter_declaration->getASTContext()
1601 ))));
1602 }
1603
1604 if (parameters.count() > 0) {
1605 if (parameters.last().type().endsWith(QLatin1String("QPrivateSignal"))) {
1606 parameters.pop_back(); // remove the QPrivateSignal argument
1607 parameters.setPrivateSignal();
1608 }
1609 }
1610
1611 if (clang_isFunctionTypeVariadic(funcType))
1612 parameters.append(QStringLiteral("..."));
1613 readParameterNamesAndAttributes(fn, cursor);
1614
1615 if (declaration && declaration->getFriendObjectKind() != clang::Decl::FOK_None) {
1616 fn->setRelatedNonmember(true);
1617 if (function_declaration && function_declaration->isThisDeclarationADefinition())
1618 fn->setHiddenFriend(true);
1619 }
1620}
1621
1622bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
1623{
1624 if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
1625 && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
1626 && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
1627 return false;
1628
1629 qsizetype lpIdx = spelling.indexOf(QChar('('));
1630 qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
1631 if (lpIdx <= 0 || rpIdx <= lpIdx)
1632 return false;
1633
1634 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1635 signature = signature.simplified();
1636 QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
1637
1638 static const QStringList attrs =
1639 QStringList() << "READ" << "MEMBER" << "WRITE"
1640 << "NOTIFY" << "CONSTANT" << "FINAL"
1641 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
1642 << "RESET" << "REVISION" << "SCRIPTABLE"
1643 << "STORED" << "USER";
1644
1645 // Find the location of the first attribute. All preceding parts
1646 // represent the property type + name.
1647 auto it = std::find_if(parts.cbegin(), parts.cend(),
1648 [](const QString &attr) -> bool {
1649 return attrs.contains(attr);
1650 });
1651
1652 if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
1653 return false;
1654
1655 QStringList typeParts;
1656 std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
1657 parts.erase(parts.cbegin(), it);
1658 QString name = typeParts.takeLast();
1659
1660 // Move the pointer operator(s) from name to type
1661 while (!name.isEmpty() && name.front() == QChar('*')) {
1662 typeParts.last().push_back(name.front());
1663 name.removeFirst();
1664 }
1665
1666 // Need at least READ or MEMBER + getter/member name
1667 if (parts.size() < 2 || name.isEmpty())
1668 return false;
1669
1670 auto *property = new PropertyNode(parent_, name);
1671 property->setAccess(Access::Public);
1672 property->setLocation(loc);
1673 property->setDataType(typeParts.join(QChar(' ')));
1674
1675 int i = 0;
1676 while (i < parts.size()) {
1677 const QString &key = parts.at(i++);
1678 // Keywords with no associated values
1679 if (key == "CONSTANT") {
1680 property->setConstant();
1681 } else if (key == "REQUIRED") {
1682 property->setRequired();
1683 }
1684 if (i < parts.size()) {
1685 QString value = parts.at(i++);
1686 if (key == "READ") {
1687 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
1688 } else if (key == "WRITE") {
1689 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
1690 property->setWritable(true);
1691 } else if (key == "MEMBER") {
1692 property->setWritable(true);
1693 } else if (key == "STORED") {
1694 property->setStored(value.toLower() == "true");
1695 } else if (key == "BINDABLE") {
1696 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
1697 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
1698 } else if (key == "RESET") {
1699 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
1700 } else if (key == "NOTIFY") {
1701 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
1702 }
1703 }
1704 }
1705 return true;
1706}
1707
1708/*!
1709 Given a comment at location \a loc, return a Node for this comment
1710 \a nextCommentLoc is the location of the next comment so the declaration
1711 must be inbetween.
1712 Returns nullptr if no suitable declaration was found between the two comments.
1713 */
1714Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
1715{
1716 ClangVisitor::SimpleLoc docloc;
1717 clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
1718 auto decl_it = declMap_.upperBound(docloc);
1719 if (decl_it == declMap_.end())
1720 return nullptr;
1721
1722 unsigned int declLine = decl_it.key().line;
1723 unsigned int nextCommentLine;
1724 clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
1725 if (nextCommentLine < declLine)
1726 return nullptr; // there is another comment before the declaration, ignore it.
1727
1728 // make sure the previous decl was finished.
1729 if (decl_it != declMap_.begin()) {
1730 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
1731 unsigned int prevDeclLine;
1732 clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
1733 if (prevDeclLine >= docloc.line) {
1734 // The previous declaration was still going. This is only valid if the previous
1735 // declaration is a parent of the next declaration.
1736 auto parent = clang_getCursorLexicalParent(*decl_it);
1737 if (!clang_equalCursors(parent, *(std::prev(decl_it))))
1738 return nullptr;
1739 }
1740 }
1741 auto *node = findNodeForCursor(qdb_, *decl_it);
1742 // borrow the parameter name from the definition
1743 if (node && node->isFunction(Genus::CPP))
1744 readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
1745 return node;
1746}
1747
1749 QDocDatabase* qdb,
1750 Config& config,
1751 const std::vector<QByteArray>& include_paths,
1752 const QList<QByteArray>& defines,
1753 std::optional<std::reference_wrapper<const PCHFile>> pch
1754) : m_qdb{qdb},
1757 m_pch{pch}
1758{
1759 m_allHeaders = config.getHeaderFiles();
1760 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
1761}
1762
1763static const char *defaultArgs_[] = {
1764 "-std=c++20",
1765#ifndef Q_OS_WIN
1766 "-fPIC",
1767#else
1768 "-fms-compatibility-version=19",
1769#endif
1770 "-DQ_QDOC",
1771 "-DQ_CLANG_QDOC",
1772 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
1773 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
1774 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
1775 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
1776 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
1777 "-Wno-constant-logical-operand",
1778 "-Wno-macro-redefined",
1779 "-Wno-nullability-completeness",
1780 "-fvisibility=default",
1781 "-ferror-limit=0",
1782 "-xc++"
1783};
1784
1785/*!
1786 Load the default arguments and the defines into \a args.
1787 Clear \a args first.
1788 */
1789void getDefaultArgs(const QList<QByteArray>& defines, std::vector<const char*>& args)
1790{
1791 args.clear();
1792 args.insert(args.begin(), std::begin(defaultArgs_), std::end(defaultArgs_));
1793
1794 // Add the defines from the qdocconf file.
1795 for (const auto &p : std::as_const(defines))
1796 args.push_back(p.constData());
1797}
1798
1799static QList<QByteArray> includePathsFromHeaders(const std::set<Config::HeaderFilePath> &allHeaders)
1800{
1801 QList<QByteArray> result;
1802 for (const auto& [header_path, _] : allHeaders) {
1803 const QByteArray path = "-I" + header_path.toLatin1();
1804 const QByteArray parent =
1805 "-I" + QDir::cleanPath(header_path + QLatin1String("/../")).toLatin1();
1806 }
1807
1808 return result;
1809}
1810
1811/*!
1812 Load the include paths into \a moreArgs. If no include paths
1813 were provided, try to guess reasonable include paths.
1814 */
1816 const std::vector<QByteArray>& include_paths,
1817 const std::set<Config::HeaderFilePath>& all_headers,
1818 std::vector<const char*>& args
1819) {
1820 if (include_paths.empty()) {
1821 /*
1822 The include paths provided are inadequate. Make a list
1823 of reasonable places to look for include files and use
1824 that list instead.
1825 */
1826 qCWarning(lcQdoc) << "No include paths passed to qdoc; guessing reasonable include paths";
1827
1828 QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir + "/../include"));
1829 args.emplace_back(QByteArray("-I" + basicIncludeDir.toLatin1()).constData());
1830
1831 auto include_paths_from_headers = includePathsFromHeaders(all_headers);
1832 args.insert(args.end(), include_paths_from_headers.begin(), include_paths_from_headers.end());
1833 } else {
1834 std::copy(include_paths.begin(), include_paths.end(), std::back_inserter(args));
1835 }
1836}
1837
1838/*!
1839 Building the PCH must be possible when there are no .cpp
1840 files, so it is moved here to its own member function, and
1841 it is called after the list of header files is complete.
1842 */
1844 QDocDatabase* qdb,
1845 QString module_header,
1846 const std::set<Config::HeaderFilePath>& all_headers,
1847 const std::vector<QByteArray>& include_paths,
1848 const QList<QByteArray>& defines,
1849 const InclusionPolicy& policy
1850) {
1851 static std::vector<const char*> arguments{};
1852
1853 if (module_header.isEmpty()) return std::nullopt;
1854
1855 getDefaultArgs(defines, arguments);
1856 getMoreArgs(include_paths, all_headers, arguments);
1857
1858 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
1859 | CXTranslationUnit_SkipFunctionBodies
1860 | CXTranslationUnit_KeepGoing);
1861
1862 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
1863
1864 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
1865 if (!pch_directory.isValid()) return std::nullopt;
1866
1867 const QByteArray module = module_header.toUtf8();
1868 QByteArray header;
1869
1870 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
1871 // A predicate for std::find_if() to locate a path to the module's header
1872 // (e.g. QtGui/QtGui) to be used as pre-compiled header
1873 struct FindPredicate
1874 {
1875 enum SearchType { Any, Module };
1876 QByteArray &candidate_;
1877 const QByteArray &module_;
1878 SearchType type_;
1879 FindPredicate(QByteArray &candidate, const QByteArray &module,
1880 SearchType type = Any)
1881 : candidate_(candidate), module_(module), type_(type)
1882 {
1883 }
1884
1885 bool operator()(const QByteArray &p) const
1886 {
1887 if (type_ != Any && !p.endsWith(module_))
1888 return false;
1889 candidate_ = p + "/";
1890 candidate_.append(module_);
1891 if (p.startsWith("-I"))
1892 candidate_ = candidate_.mid(2);
1893 return QFile::exists(QString::fromUtf8(candidate_));
1894 }
1895 };
1896
1897 // First, search for an include path that contains the module name, then any path
1898 QByteArray candidate;
1899 auto it = std::find_if(include_paths.begin(), include_paths.end(),
1900 FindPredicate(candidate, module, FindPredicate::Module));
1901 if (it == include_paths.end())
1902 it = std::find_if(include_paths.begin(), include_paths.end(),
1903 FindPredicate(candidate, module, FindPredicate::Any));
1904 if (it != include_paths.end())
1905 header = std::move(candidate);
1906
1907 if (header.isEmpty()) {
1908 qWarning() << "(qdoc) Could not find the module header in include paths for module"
1909 << module << " (include paths: " << include_paths << ")";
1910 qWarning() << " Artificial module header built from header dirs in qdocconf "
1911 "file";
1912 }
1913 arguments.push_back("-xc++");
1914
1915 TranslationUnit tu;
1916
1917 QString tmpHeader = pch_directory.path() + "/" + module;
1918 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
1919 QTextStream out(&tmpHeaderFile);
1920 if (header.isEmpty()) {
1921 for (const auto& [header_path, header_name] : all_headers) {
1922 bool shouldInclude = !header_name.startsWith("moc_"_L1);
1923
1924 // Conditionally include private headers based on showInternal setting
1925 if (header_name.endsWith("_p.h"_L1))
1926 shouldInclude = shouldInclude && policy.showInternal;
1927
1928 if (shouldInclude) {
1929 out << "#include \"" << header_path << "/" << header_name << "\"\n";
1930 }
1931 }
1932 } else {
1933 QFileInfo headerFile(header);
1934 if (!headerFile.exists()) {
1935 qWarning() << "Could not find module header file" << header;
1936 return std::nullopt;
1937 }
1938
1939 out << "#include \"" << header << "\"\n";
1940
1941 if (policy.showInternal) {
1942 for (const auto& [header_path, header_name] : all_headers) {
1943 bool shouldInclude = !header_name.startsWith("moc_"_L1);
1944 if (header_name.endsWith("_p.h"_L1) && shouldInclude)
1945 out << "#include \"" << header_path << "/" << header_name << "\"\n";
1946 }
1947 }
1948 }
1949 }
1950
1951 CXErrorCode err =
1952 clang_parseTranslationUnit2(index, tmpHeader.toLatin1().data(), arguments.data(),
1953 static_cast<int>(arguments.size()), nullptr, 0,
1954 flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
1955 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
1956 << ") returns" << err;
1957
1959
1960 if (err || !tu) {
1961 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
1962 return std::nullopt;
1963 }
1964
1965 QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
1966 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
1967 clang_defaultSaveOptions(tu));
1968 if (error) {
1969 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
1970 return std::nullopt;
1971 }
1972
1973 // Visit the header now, as token from pre-compiled header won't be visited
1974 // later
1975 CXCursor cur = clang_getTranslationUnitCursor(tu);
1976 auto &config = Config::instance();
1977 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
1978 visitor.visitChildren(cur);
1979 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
1980
1981 return std::make_optional(PCHFile{std::move(pch_directory), std::move(pch_name)});
1982}
1983
1984static float getUnpatchedVersion(QString t)
1985{
1986 if (t.count(QChar('.')) > 1)
1987 t.truncate(t.lastIndexOf(QChar('.')));
1988 return t.toFloat();
1989}
1990
1991/*!
1992 Get ready to parse the C++ cpp file identified by \a filePath
1993 and add its parsed contents to the database. \a location is
1994 used for reporting errors.
1995
1996 If parsing C++ header file as source, do not use the precompiled
1997 header as the source file itself is likely already included in the
1998 PCH and therefore interferes visiting the TU's children.
1999 */
2000ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
2001{
2002 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2003 | CXTranslationUnit_SkipFunctionBodies
2004 | CXTranslationUnit_KeepGoing);
2005
2006 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2007
2008 getDefaultArgs(m_defines, m_args);
2009 if (m_pch && !filePath.endsWith(".mm")
2010 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2011 m_args.push_back("-w");
2012 m_args.push_back("-include-pch");
2013 m_args.push_back((*m_pch).get().name.constData());
2014 }
2015 getMoreArgs(m_includePaths, m_allHeaders, m_args);
2016
2017 TranslationUnit tu;
2018 CXErrorCode err =
2019 clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), m_args.data(),
2020 static_cast<int>(m_args.size()), nullptr, 0, flags_, &tu.tu);
2021 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
2022 << ") returns" << err;
2024
2025 if (err || !tu) {
2026 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
2027 return {};
2028 }
2029
2030 ParsedCppFileIR parse_result{};
2031
2032 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2033 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2034 visitor.visitChildren(tuCur);
2035
2036 CXToken *tokens;
2037 unsigned int numTokens = 0;
2038 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2039 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2040
2041 for (unsigned int i = 0; i < numTokens; ++i) {
2042 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2043 continue;
2044 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2045 if (!comment.startsWith("/*!"))
2046 continue;
2047
2048 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2049 auto loc = fromCXSourceLocation(commentLoc);
2050 auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
2051 Doc::trimCStyleComment(loc, comment);
2052
2053 // Doc constructor parses the comment.
2054 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2055 if (hasTooManyTopics(doc))
2056 continue;
2057
2058 if (doc.topicsUsed().isEmpty()) {
2059 Node *n = nullptr;
2060 if (i + 1 < numTokens) {
2061 // Try to find the next declaration.
2062 CXSourceLocation nextCommentLoc = commentLoc;
2063 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2064 ++i; // already skip all the tokens that are not comments
2065 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2066 n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
2067 }
2068
2069 if (n) {
2070 parse_result.tied.emplace_back(TiedDocumentation{doc, n});
2071 } else if (CodeParser::isWorthWarningAbout(doc)) {
2072 bool future = false;
2073 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
2074 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
2075 if (getUnpatchedVersion(std::move(sinceVersion)) >
2076 getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
2077 future = true;
2078 }
2079 if (!future) {
2080 doc.location().warning(
2081 QStringLiteral("Cannot tie this documentation to anything"),
2082 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
2083 "topic command (e.g., '\\%1', '\\%2') in the "
2084 "comment and no function definition following "
2085 "the comment.")
2086 .arg(COMMAND_FN, COMMAND_PAGE));
2087 }
2088 }
2089 } else {
2090 parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
2091
2092 CXCursor cur = clang_getCursor(tu, commentLoc);
2093 while (true) {
2094 CXCursorKind kind = clang_getCursorKind(cur);
2095 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2096 break;
2097 if (kind == CXCursor_Namespace) {
2098 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2099 }
2100 cur = clang_getCursorLexicalParent(cur);
2101 }
2102 }
2103 }
2104
2105 clang_disposeTokens(tu, tokens, numTokens);
2106 m_namespaceScope.clear();
2107 s_fn.clear();
2108
2109 return parse_result;
2110}
2111
2112/*!
2113 Use clang to parse the function signature from a function
2114 command. \a location is used for reporting errors. \a fnSignature
2115 is the string to parse. It is always a function decl.
2116 \a idTag is the optional bracketed argument passed to \\fn, or
2117 an empty string.
2118 \a context is a string list representing the scope (namespaces)
2119 under which the function is declared.
2120
2121 Returns a variant that's either a Node instance tied to the
2122 function declaration, or a parsing failure for later processing.
2123 */
2124std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
2125 const QString &idTag, QStringList context)
2126{
2127 Node *fnNode = nullptr;
2128 /*
2129 If the \fn command begins with a tag, then don't try to
2130 parse the \fn command with clang. Use the tag to search
2131 for the correct function node. It is an error if it can
2132 not be found. Return 0 in that case.
2133 */
2134 if (!idTag.isEmpty()) {
2135 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2136 if (!fnNode) {
2137 location.error(
2138 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
2139 } else {
2140 /*
2141 The function node was found. Use the formal
2142 parameter names from the \fn command, because
2143 they will be the names used in the documentation.
2144 */
2145 auto *fn = static_cast<FunctionNode *>(fnNode);
2146 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
2147 if (leftParenSplit.size() > 1) {
2148 QStringList rightParenSplit = leftParenSplit[1].split(')');
2149 if (!rightParenSplit.empty()) {
2150 QString params = rightParenSplit[0];
2151 if (!params.isEmpty()) {
2152 QStringList commaSplit = params.split(',');
2153 Parameters &parameters = fn->parameters();
2154 if (parameters.count() == commaSplit.size()) {
2155 for (int i = 0; i < parameters.count(); ++i) {
2156 QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
2157 if (blankSplit.size() > 1) {
2158 QString pName = blankSplit.last();
2159 // Remove any non-letters from the start of parameter name
2160 auto it = std::find_if(std::begin(pName), std::end(pName),
2161 [](const QChar &c) { return c.isLetter(); });
2162 parameters[i].setName(
2163 pName.remove(0, std::distance(std::begin(pName), it)));
2164 }
2165 }
2166 }
2167 }
2168 }
2169 }
2170 }
2171 return fnNode;
2172 }
2173 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2174 | CXTranslationUnit_SkipFunctionBodies
2175 | CXTranslationUnit_KeepGoing);
2176
2177 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2178
2179 getDefaultArgs(m_defines, m_args);
2180
2181 if (m_pch) {
2182 m_args.push_back("-w");
2183 m_args.push_back("-include-pch");
2184 m_args.push_back((*m_pch).get().name.constData());
2185 }
2186
2187 TranslationUnit tu;
2188 QByteArray s_fn{};
2189 for (const auto &ns : std::as_const(context))
2190 s_fn.prepend("namespace " + ns.toUtf8() + " {");
2191 s_fn += fnSignature.toUtf8();
2192 if (!s_fn.endsWith(";"))
2193 s_fn += "{ }";
2194 s_fn.append(context.size(), '}');
2195
2196 const char *dummyFileName = fnDummyFileName;
2197 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2198 static_cast<unsigned long>(s_fn.size()) };
2199 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, m_args.data(),
2200 int(m_args.size()), &unsavedFile, 1, flags, &tu.tu);
2201 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
2202 << ") returns" << err;
2204 if (err || !tu) {
2205 location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
2206 return fnNode;
2207 } else {
2208 /*
2209 Always visit the tu if one is constructed, because
2210 it might be possible to find the correct node, even
2211 if clang detected diagnostics. Only bother to report
2212 the diagnostics if they stop us finding the node.
2213 */
2214 CXCursor cur = clang_getTranslationUnitCursor(tu);
2215 auto &config = Config::instance();
2216 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2217 bool ignoreSignature = false;
2218 visitor.visitFnArg(cur, &fnNode, ignoreSignature);
2219
2220 if (!fnNode) {
2221 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2222 const auto &config = Config::instance();
2223 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2224 return FnMatchError{ fnSignature, location };
2225 }
2226 }
2227 }
2228 return fnNode;
2229}
2230
2231QT_END_NAMESPACE
Access
Definition access.h:11
static const clang::Decl * get_cursor_declaration(CXCursor cursor)
Returns the underlying Decl that cursor represents.
static QString reconstructQualifiedPathForCursor(CXCursor cur)
Reconstruct the qualified path name of a function that is being overridden.
static std::optional< QString > classNameFromParameterType(clang::QualType param_type)
QString functionName(CXCursor cursor)
Returns the function name from a given cursor representing a function declaration.
static std::string get_default_value_initializer_as_string(const clang::TemplateTemplateParmDecl *parameter)
static QString fromCXString(CXString &&string)
convert a CXString to a QString, and dispose the CXString
static QDebug operator<<(QDebug debug, const std::vector< T > &v)
static QString getSpelling(CXSourceRange range)
static void setOverridesForFunction(FunctionNode *fn, CXCursor cursor)
static QList< QByteArray > includePathsFromHeaders(const std::set< Config::HeaderFilePath > &allHeaders)
static const auto kClangDontDisplayDiagnostics
void getMoreArgs(const std::vector< QByteArray > &include_paths, const std::set< Config::HeaderFilePath > &all_headers, std::vector< const char * > &args)
Load the include paths into moreArgs.
static std::string get_default_value_initializer_as_string(const clang::ParmVarDecl *parameter)
void getDefaultArgs(const QList< QByteArray > &defines, std::vector< const char * > &args)
Load the default arguments and the defines into args.
static std::string get_expression_as_string(const clang::Expr *expression, const clang::ASTContext &declaration_context)
bool visitChildrenLambda(CXCursor cursor, T &&lambda)
Call clang_visitChildren on the given cursor with the lambda as a callback T can be any functor that ...
static std::string get_default_value_initializer_as_string(const clang::NamedDecl *declaration)
static RelaxedTemplateDeclaration get_template_declaration(const clang::TemplateDecl *template_declaration)
static std::string get_default_value_initializer_as_string(const clang::NonTypeTemplateParmDecl *parameter)
static QString fromCache(const QByteArray &cache, unsigned int offset1, unsigned int offset2)
static float getUnpatchedVersion(QString t)
static Location fromCXSourceLocation(CXSourceLocation location)
convert a CXSourceLocation to a qdoc Location
static QString cleanAnonymousTypeName(const QString &typeName)
static Access fromCX_CXXAccessSpecifier(CX_CXXAccessSpecifier spec)
convert a CX_CXXAccessSpecifier to Node::Access
static std::string get_default_value_initializer_as_string(const clang::TemplateTypeParmDecl *parameter)
constexpr const char fnDummyFileName[]
static CXTranslationUnit_Flags flags_
static Node * findNodeForCursor(QDocDatabase *qdb, CXCursor cur)
Find the node from the QDocDatabase qdb that corresponds to the declaration represented by the cursor...
static void autoGenerateSmfDoc(FunctionNode *fn, const QString &className)
static std::string get_fully_qualified_type_name(clang::QualType type, const clang::ASTContext &declaration_context)
Returns a string representing the name of type as if it was referred to at the end of the translation...
static void printDiagnostics(const CXTranslationUnit &translationUnit)
static const char * defaultArgs_[]
std::optional< PCHFile > buildPCH(QDocDatabase *qdb, QString module_header, const std::set< Config::HeaderFilePath > &all_headers, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, const InclusionPolicy &policy)
Building the PCH must be possible when there are no .cpp files, so it is moved here to its own member...
static QString readFile(CXFile cxFile, unsigned int offset1, unsigned int offset2)
void addChild(Node *child)
Adds the child to this node's child list and sets the child's parent pointer to this Aggregate.
ParsedCppFileIR parse_cpp_file(const QString &filePath)
Get ready to parse the C++ cpp file identified by filePath and add its parsed contents to the databas...
ClangCodeParser(QDocDatabase *qdb, Config &, const std::vector< QByteArray > &include_paths, const QList< QByteArray > &defines, std::optional< std::reference_wrapper< const PCHFile > > pch)
Node * nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
Given a comment at location loc, return a Node for this comment nextCommentLoc is the location of the...
CXChildVisitResult visitChildren(CXCursor cursor)
QDocDatabase * qdocDB()
ClangVisitor(QDocDatabase *qdb, const std::set< Config::HeaderFilePath > &allHeaders, const Config::InternalFilePatterns &internalFilePatterns)
CXChildVisitResult visitFnArg(CXCursor cursor, Node **fnNode, bool &ignoreSignature)
The ClassNode represents a C++ class.
Definition classnode.h:23
static bool isWorthWarningAbout(const Doc &doc)
Test for whether a doc comment warrants warnings.
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:85
Definition doc.h:31
const Location & location() const
Returns the starting location of a qdoc comment.
Definition doc.cpp:90
void markAutoGenerated()
Marks this documentation as auto-generated by QDoc.
Definition doc.cpp:251
TopicList topicsUsed() const
Returns a reference to the list of topic commands used in the current qdoc comment.
Definition doc.cpp:271
This node is used to represent any kind of function being documented.
void setConst(bool b)
void markDeletedAsWritten()
void setStatic(bool b)
void setVirtualness(Virtualness virtualness)
bool isNonvirtual() const
bool isMAssign() const
void setInvokable(bool b)
bool isCAssign() const
void setRef(bool b)
bool isDtor() const
void setOverride(bool b)
void setRefRef(bool b)
bool isSpecialMemberFunction() const
void markConstexpr()
bool isDeletedAsWritten() const
void markExplicit()
void markExplicitlyDefaulted()
bool isCCtor() const
bool isMCtor() const
bool isCtor() const
void setMetaness(Metaness metaness)
bool isExplicitlyDefaulted() const
Parameters & parameters()
void setHiddenFriend(bool b)
The Location class provides a way to mark a location in a file.
Definition location.h:20
void setColumnNo(int no)
Definition location.h:43
void setLineNo(int no)
Definition location.h:42
This class represents a C++ namespace.
This class describes one instance of using the Q_PROPERTY macro.
This class provides exclusive access to the qdoc database, which consists of a forrest of trees and a...
NamespaceNode * primaryTreeRoot()
Returns a pointer to the root node of the primary tree.
Status
Specifies the status of the QQmlIncubator.
#define COMMAND_SINCE
Definition codeparser.h:75
#define COMMAND_FN
Definition codeparser.h:26
#define COMMAND_PAGE
Definition codeparser.h:44
#define CONFIG_VERSION
Definition config.h:447
#define CONFIG_DOCUMENTATIONINHEADERS
Definition config.h:377
bool hasTooManyTopics(const Doc &doc)
Checks if there are too many topic commands in doc.
NodeType
Definition genustypes.h:150
This namespace holds QDoc-internal utility methods.
Definition utilities.h:20
std::string getFullyQualifiedName(QualType QT, const ASTContext &Ctx, const PrintingPolicy &Policy, bool WithGlobalNsPrefix=false)
QList< Node * > NodeVector
Definition node.h:47
#define assert
@ Public
Definition access.h:11
@ Private
Definition access.h:11
@ Protected
Definition access.h:11
@ Internal
Definition status.h:15
Returns the spelling in the file for a source range.
std::variant< Node *, FnMatchError > operator()(const Location &location, const QString &fnSignature, const QString &idTag, QStringList context)
Use clang to parse the function signature from a function command.
Encapsulates information about.
Definition parsererror.h:13
The Node class is the base class for all the nodes in QDoc's parse tree.
void setAccess(Access t)
Sets the node's access type to t.
Definition node.h:170
bool isNamespace() const
Returns true if the node type is Namespace.
Definition node.h:108
bool isTypedef() const
Returns true if the node type is Typedef.
Definition node.h:126
bool isVariable() const
Returns true if the node type is Variable.
Definition node.h:131
void setLocation(const Location &t)
Sets the node's declaration location, its definition location, or both, depending on the suffix of th...
Definition node.cpp:899
virtual bool isAggregate() const
Returns true if this node is an aggregate, which means it inherits Aggregate and can therefore have c...
Definition node.h:136
virtual void setRelatedNonmember(bool b)
Sets a flag in the node indicating whether this node is a related nonmember of something.
Definition node.h:185
const Location & location() const
If this node's definition location is empty, this function returns this node's declaration location.
Definition node.h:231
bool isFunction(Genus g=Genus::DontCare) const
Returns true if this is a FunctionNode and its Genus is set to g.
Definition node.h:100
void setDoc(const Doc &doc, bool replace=false)
Sets this Node's Doc to doc.
Definition node.cpp:556
bool isClass() const
Returns true if the node type is Class.
Definition node.h:90
bool hasDoc() const
Returns true if this node is documented, or it represents a documented node read from the index ('had...
Definition node.cpp:932
virtual bool isClassNode() const
Returns true if this is an instance of ClassNode.
Definition node.h:143
A class for parsing and managing a function parameter list.
Definition main.cpp:28
Parameter & operator[](int index)
Definition parameters.h:77
void pop_back()
Definition parameters.h:81
void reserve(int count)
Definition parameters.h:73
void clear()
Definition parameters.h:62
Parameter & last()
Definition parameters.h:75
int count() const
Definition parameters.h:72
void setPrivateSignal()
Definition parameters.h:82
CXTranslationUnit tu