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 Q_ASSERT(function_declaration);
1618 if (function_declaration->isThisDeclarationADefinition())
1619 fn->setHiddenFriend(true);
1620 }
1621}
1622
1623bool ClangVisitor::parseProperty(const QString &spelling, const Location &loc)
1624{
1625 if (!spelling.startsWith(QLatin1String("Q_PROPERTY"))
1626 && !spelling.startsWith(QLatin1String("QDOC_PROPERTY"))
1627 && !spelling.startsWith(QLatin1String("Q_OVERRIDE")))
1628 return false;
1629
1630 qsizetype lpIdx = spelling.indexOf(QChar('('));
1631 qsizetype rpIdx = spelling.lastIndexOf(QChar(')'));
1632 if (lpIdx <= 0 || rpIdx <= lpIdx)
1633 return false;
1634
1635 QString signature = spelling.mid(lpIdx + 1, rpIdx - lpIdx - 1);
1636 signature = signature.simplified();
1637 QStringList parts = signature.split(QChar(' '), Qt::SkipEmptyParts);
1638
1639 static const QStringList attrs =
1640 QStringList() << "READ" << "MEMBER" << "WRITE"
1641 << "NOTIFY" << "CONSTANT" << "FINAL"
1642 << "REQUIRED" << "BINDABLE" << "DESIGNABLE"
1643 << "RESET" << "REVISION" << "SCRIPTABLE"
1644 << "STORED" << "USER";
1645
1646 // Find the location of the first attribute. All preceding parts
1647 // represent the property type + name.
1648 auto it = std::find_if(parts.cbegin(), parts.cend(),
1649 [](const QString &attr) -> bool {
1650 return attrs.contains(attr);
1651 });
1652
1653 if (it == parts.cend() || std::distance(parts.cbegin(), it) < 2)
1654 return false;
1655
1656 QStringList typeParts;
1657 std::copy(parts.cbegin(), it, std::back_inserter(typeParts));
1658 parts.erase(parts.cbegin(), it);
1659 QString name = typeParts.takeLast();
1660
1661 // Move the pointer operator(s) from name to type
1662 while (!name.isEmpty() && name.front() == QChar('*')) {
1663 typeParts.last().push_back(name.front());
1664 name.removeFirst();
1665 }
1666
1667 // Need at least READ or MEMBER + getter/member name
1668 if (parts.size() < 2 || name.isEmpty())
1669 return false;
1670
1671 auto *property = new PropertyNode(parent_, name);
1672 property->setAccess(Access::Public);
1673 property->setLocation(loc);
1674 property->setDataType(typeParts.join(QChar(' ')));
1675
1676 int i = 0;
1677 while (i < parts.size()) {
1678 const QString &key = parts.at(i++);
1679 // Keywords with no associated values
1680 if (key == "CONSTANT") {
1681 property->setConstant();
1682 } else if (key == "REQUIRED") {
1683 property->setRequired();
1684 }
1685 if (i < parts.size()) {
1686 QString value = parts.at(i++);
1687 if (key == "READ") {
1688 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Getter);
1689 } else if (key == "WRITE") {
1690 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Setter);
1691 property->setWritable(true);
1692 } else if (key == "MEMBER") {
1693 property->setWritable(true);
1694 } else if (key == "STORED") {
1695 property->setStored(value.toLower() == "true");
1696 } else if (key == "BINDABLE") {
1697 property->setPropertyType(PropertyNode::PropertyType::BindableProperty);
1698 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Bindable);
1699 } else if (key == "RESET") {
1700 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Resetter);
1701 } else if (key == "NOTIFY") {
1702 qdb_->addPropertyFunction(property, value, PropertyNode::FunctionRole::Notifier);
1703 }
1704 }
1705 }
1706 return true;
1707}
1708
1709/*!
1710 Given a comment at location \a loc, return a Node for this comment
1711 \a nextCommentLoc is the location of the next comment so the declaration
1712 must be inbetween.
1713 Returns nullptr if no suitable declaration was found between the two comments.
1714 */
1715Node *ClangVisitor::nodeForCommentAtLocation(CXSourceLocation loc, CXSourceLocation nextCommentLoc)
1716{
1717 ClangVisitor::SimpleLoc docloc;
1718 clang_getPresumedLocation(loc, nullptr, &docloc.line, &docloc.column);
1719 auto decl_it = declMap_.upperBound(docloc);
1720 if (decl_it == declMap_.end())
1721 return nullptr;
1722
1723 unsigned int declLine = decl_it.key().line;
1724 unsigned int nextCommentLine;
1725 clang_getPresumedLocation(nextCommentLoc, nullptr, &nextCommentLine, nullptr);
1726 if (nextCommentLine < declLine)
1727 return nullptr; // there is another comment before the declaration, ignore it.
1728
1729 // make sure the previous decl was finished.
1730 if (decl_it != declMap_.begin()) {
1731 CXSourceLocation prevDeclEnd = clang_getRangeEnd(clang_getCursorExtent(*(std::prev(decl_it))));
1732 unsigned int prevDeclLine;
1733 clang_getPresumedLocation(prevDeclEnd, nullptr, &prevDeclLine, nullptr);
1734 if (prevDeclLine >= docloc.line) {
1735 // The previous declaration was still going. This is only valid if the previous
1736 // declaration is a parent of the next declaration.
1737 auto parent = clang_getCursorLexicalParent(*decl_it);
1738 if (!clang_equalCursors(parent, *(std::prev(decl_it))))
1739 return nullptr;
1740 }
1741 }
1742 auto *node = findNodeForCursor(qdb_, *decl_it);
1743 // borrow the parameter name from the definition
1744 if (node && node->isFunction(Genus::CPP))
1745 readParameterNamesAndAttributes(static_cast<FunctionNode *>(node), *decl_it);
1746 return node;
1747}
1748
1750 QDocDatabase* qdb,
1751 Config& config,
1752 const std::vector<QByteArray>& include_paths,
1753 const QList<QByteArray>& defines,
1754 std::optional<std::reference_wrapper<const PCHFile>> pch
1755) : m_qdb{qdb},
1758 m_pch{pch}
1759{
1760 m_allHeaders = config.getHeaderFiles();
1761 m_internalFilePatterns = config.getInternalFilePatternsCompiled();
1762}
1763
1764static const char *defaultArgs_[] = {
1765 "-std=c++20",
1766#ifndef Q_OS_WIN
1767 "-fPIC",
1768#else
1769 "-fms-compatibility-version=19",
1770#endif
1771 "-DQ_QDOC",
1772 "-DQ_CLANG_QDOC",
1773 "-DQT_DISABLE_DEPRECATED_UP_TO=0",
1774 "-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);",
1775 "-DQT_ANNOTATE_CLASS2(type,a1,a2)=static_assert(sizeof(#a1,#a2),#type);",
1776 "-DQT_ANNOTATE_FUNCTION(a)=__attribute__((annotate(#a)))",
1777 "-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))",
1778 "-Wno-constant-logical-operand",
1779 "-Wno-macro-redefined",
1780 "-Wno-nullability-completeness",
1781 "-fvisibility=default",
1782 "-ferror-limit=0",
1783 "-xc++"
1784};
1785
1786/*!
1787 Load the default arguments and the defines into \a args.
1788 Clear \a args first.
1789 */
1790void getDefaultArgs(const QList<QByteArray>& defines, std::vector<const char*>& args)
1791{
1792 args.clear();
1793 args.insert(args.begin(), std::begin(defaultArgs_), std::end(defaultArgs_));
1794
1795 // Add the defines from the qdocconf file.
1796 for (const auto &p : std::as_const(defines))
1797 args.push_back(p.constData());
1798}
1799
1800static QList<QByteArray> includePathsFromHeaders(const std::set<Config::HeaderFilePath> &allHeaders)
1801{
1802 QList<QByteArray> result;
1803 for (const auto& [header_path, _] : allHeaders) {
1804 const QByteArray path = "-I" + header_path.toLatin1();
1805 const QByteArray parent =
1806 "-I" + QDir::cleanPath(header_path + QLatin1String("/../")).toLatin1();
1807 }
1808
1809 return result;
1810}
1811
1812/*!
1813 Load the include paths into \a moreArgs. If no include paths
1814 were provided, try to guess reasonable include paths.
1815 */
1817 const std::vector<QByteArray>& include_paths,
1818 const std::set<Config::HeaderFilePath>& all_headers,
1819 std::vector<const char*>& args
1820) {
1821 if (include_paths.empty()) {
1822 /*
1823 The include paths provided are inadequate. Make a list
1824 of reasonable places to look for include files and use
1825 that list instead.
1826 */
1827 qCWarning(lcQdoc) << "No include paths passed to qdoc; guessing reasonable include paths";
1828
1829 QString basicIncludeDir = QDir::cleanPath(QString(Config::installDir + "/../include"));
1830 args.emplace_back(QByteArray("-I" + basicIncludeDir.toLatin1()).constData());
1831
1832 auto include_paths_from_headers = includePathsFromHeaders(all_headers);
1833 args.insert(args.end(), include_paths_from_headers.begin(), include_paths_from_headers.end());
1834 } else {
1835 std::copy(include_paths.begin(), include_paths.end(), std::back_inserter(args));
1836 }
1837}
1838
1839/*!
1840 Building the PCH must be possible when there are no .cpp
1841 files, so it is moved here to its own member function, and
1842 it is called after the list of header files is complete.
1843 */
1845 QDocDatabase* qdb,
1846 QString module_header,
1847 const std::set<Config::HeaderFilePath>& all_headers,
1848 const std::vector<QByteArray>& include_paths,
1849 const QList<QByteArray>& defines,
1850 const InclusionPolicy& policy
1851) {
1852 static std::vector<const char*> arguments{};
1853
1854 if (module_header.isEmpty()) return std::nullopt;
1855
1856 getDefaultArgs(defines, arguments);
1857 getMoreArgs(include_paths, all_headers, arguments);
1858
1859 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
1860 | CXTranslationUnit_SkipFunctionBodies
1861 | CXTranslationUnit_KeepGoing);
1862
1863 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
1864
1865 QTemporaryDir pch_directory{QDir::tempPath() + QLatin1String("/qdoc_pch")};
1866 if (!pch_directory.isValid()) return std::nullopt;
1867
1868 const QByteArray module = module_header.toUtf8();
1869 QByteArray header;
1870
1871 qCDebug(lcQdoc) << "Build and visit PCH for" << module_header;
1872 // A predicate for std::find_if() to locate a path to the module's header
1873 // (e.g. QtGui/QtGui) to be used as pre-compiled header
1874 struct FindPredicate
1875 {
1876 enum SearchType { Any, Module };
1877 QByteArray &candidate_;
1878 const QByteArray &module_;
1879 SearchType type_;
1880 FindPredicate(QByteArray &candidate, const QByteArray &module,
1881 SearchType type = Any)
1882 : candidate_(candidate), module_(module), type_(type)
1883 {
1884 }
1885
1886 bool operator()(const QByteArray &p) const
1887 {
1888 if (type_ != Any && !p.endsWith(module_))
1889 return false;
1890 candidate_ = p + "/";
1891 candidate_.append(module_);
1892 if (p.startsWith("-I"))
1893 candidate_ = candidate_.mid(2);
1894 return QFile::exists(QString::fromUtf8(candidate_));
1895 }
1896 };
1897
1898 // First, search for an include path that contains the module name, then any path
1899 QByteArray candidate;
1900 auto it = std::find_if(include_paths.begin(), include_paths.end(),
1901 FindPredicate(candidate, module, FindPredicate::Module));
1902 if (it == include_paths.end())
1903 it = std::find_if(include_paths.begin(), include_paths.end(),
1904 FindPredicate(candidate, module, FindPredicate::Any));
1905 if (it != include_paths.end())
1906 header = std::move(candidate);
1907
1908 if (header.isEmpty()) {
1909 qWarning() << "(qdoc) Could not find the module header in include paths for module"
1910 << module << " (include paths: " << include_paths << ")";
1911 qWarning() << " Artificial module header built from header dirs in qdocconf "
1912 "file";
1913 }
1914 arguments.push_back("-xc++");
1915
1916 TranslationUnit tu;
1917
1918 QString tmpHeader = pch_directory.path() + "/" + module;
1919 if (QFile tmpHeaderFile(tmpHeader); tmpHeaderFile.open(QIODevice::Text | QIODevice::WriteOnly)) {
1920 QTextStream out(&tmpHeaderFile);
1921 if (header.isEmpty()) {
1922 for (const auto& [header_path, header_name] : all_headers) {
1923 bool shouldInclude = !header_name.startsWith("moc_"_L1);
1924
1925 // Conditionally include private headers based on showInternal setting
1926 if (header_name.endsWith("_p.h"_L1))
1927 shouldInclude = shouldInclude && policy.showInternal;
1928
1929 if (shouldInclude) {
1930 out << "#include \"" << header_path << "/" << header_name << "\"\n";
1931 }
1932 }
1933 } else {
1934 QFileInfo headerFile(header);
1935 if (!headerFile.exists()) {
1936 qWarning() << "Could not find module header file" << header;
1937 return std::nullopt;
1938 }
1939
1940 out << "#include \"" << header << "\"\n";
1941
1942 if (policy.showInternal) {
1943 for (const auto& [header_path, header_name] : all_headers) {
1944 bool shouldInclude = !header_name.startsWith("moc_"_L1);
1945 if (header_name.endsWith("_p.h"_L1) && shouldInclude)
1946 out << "#include \"" << header_path << "/" << header_name << "\"\n";
1947 }
1948 }
1949 }
1950 }
1951
1952 CXErrorCode err =
1953 clang_parseTranslationUnit2(index, tmpHeader.toLatin1().data(), arguments.data(),
1954 static_cast<int>(arguments.size()), nullptr, 0,
1955 flags_ | CXTranslationUnit_ForSerialization, &tu.tu);
1956 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << tmpHeader << arguments
1957 << ") returns" << err;
1958
1960
1961 if (err || !tu) {
1962 qCCritical(lcQdoc) << "Could not create PCH file for " << module_header;
1963 return std::nullopt;
1964 }
1965
1966 QByteArray pch_name = pch_directory.path().toUtf8() + "/" + module + ".pch";
1967 auto error = clang_saveTranslationUnit(tu, pch_name.constData(),
1968 clang_defaultSaveOptions(tu));
1969 if (error) {
1970 qCCritical(lcQdoc) << "Could not save PCH file for" << module_header;
1971 return std::nullopt;
1972 }
1973
1974 // Visit the header now, as token from pre-compiled header won't be visited
1975 // later
1976 CXCursor cur = clang_getTranslationUnitCursor(tu);
1977 auto &config = Config::instance();
1978 ClangVisitor visitor(qdb, all_headers, config.getInternalFilePatternsCompiled());
1979 visitor.visitChildren(cur);
1980 qCDebug(lcQdoc) << "PCH built and visited for" << module_header;
1981
1982 return std::make_optional(PCHFile{std::move(pch_directory), std::move(pch_name)});
1983}
1984
1985static float getUnpatchedVersion(QString t)
1986{
1987 if (t.count(QChar('.')) > 1)
1988 t.truncate(t.lastIndexOf(QChar('.')));
1989 return t.toFloat();
1990}
1991
1992/*!
1993 Get ready to parse the C++ cpp file identified by \a filePath
1994 and add its parsed contents to the database. \a location is
1995 used for reporting errors.
1996
1997 If parsing C++ header file as source, do not use the precompiled
1998 header as the source file itself is likely already included in the
1999 PCH and therefore interferes visiting the TU's children.
2000 */
2001ParsedCppFileIR ClangCodeParser::parse_cpp_file(const QString &filePath)
2002{
2003 flags_ = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2004 | CXTranslationUnit_SkipFunctionBodies
2005 | CXTranslationUnit_KeepGoing);
2006
2007 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2008
2009 getDefaultArgs(m_defines, m_args);
2010 if (m_pch && !filePath.endsWith(".mm")
2011 && !std::holds_alternative<CppHeaderSourceFile>(tag_source_file(filePath).second)) {
2012 m_args.push_back("-w");
2013 m_args.push_back("-include-pch");
2014 m_args.push_back((*m_pch).get().name.constData());
2015 }
2016 getMoreArgs(m_includePaths, m_allHeaders, m_args);
2017
2018 TranslationUnit tu;
2019 CXErrorCode err =
2020 clang_parseTranslationUnit2(index, filePath.toLocal8Bit(), m_args.data(),
2021 static_cast<int>(m_args.size()), nullptr, 0, flags_, &tu.tu);
2022 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << filePath << m_args
2023 << ") returns" << err;
2025
2026 if (err || !tu) {
2027 qWarning() << "(qdoc) Could not parse source file" << filePath << " error code:" << err;
2028 return {};
2029 }
2030
2031 ParsedCppFileIR parse_result{};
2032
2033 CXCursor tuCur = clang_getTranslationUnitCursor(tu);
2034 ClangVisitor visitor(m_qdb, m_allHeaders, m_internalFilePatterns);
2035 visitor.visitChildren(tuCur);
2036
2037 CXToken *tokens;
2038 unsigned int numTokens = 0;
2039 const QSet<QString> &commands = CppCodeParser::topic_commands + CppCodeParser::meta_commands;
2040 clang_tokenize(tu, clang_getCursorExtent(tuCur), &tokens, &numTokens);
2041
2042 for (unsigned int i = 0; i < numTokens; ++i) {
2043 if (clang_getTokenKind(tokens[i]) != CXToken_Comment)
2044 continue;
2045 QString comment = fromCXString(clang_getTokenSpelling(tu, tokens[i]));
2046 if (!comment.startsWith("/*!"))
2047 continue;
2048
2049 auto commentLoc = clang_getTokenLocation(tu, tokens[i]);
2050 auto loc = fromCXSourceLocation(commentLoc);
2051 auto end_loc = fromCXSourceLocation(clang_getRangeEnd(clang_getTokenExtent(tu, tokens[i])));
2052 Doc::trimCStyleComment(loc, comment);
2053
2054 // Doc constructor parses the comment.
2055 Doc doc(loc, end_loc, comment, commands, CppCodeParser::topic_commands);
2056 if (hasTooManyTopics(doc))
2057 continue;
2058
2059 if (doc.topicsUsed().isEmpty()) {
2060 Node *n = nullptr;
2061 if (i + 1 < numTokens) {
2062 // Try to find the next declaration.
2063 CXSourceLocation nextCommentLoc = commentLoc;
2064 while (i + 2 < numTokens && clang_getTokenKind(tokens[i + 1]) != CXToken_Comment)
2065 ++i; // already skip all the tokens that are not comments
2066 nextCommentLoc = clang_getTokenLocation(tu, tokens[i + 1]);
2067 n = visitor.nodeForCommentAtLocation(commentLoc, nextCommentLoc);
2068 }
2069
2070 if (n) {
2071 parse_result.tied.emplace_back(TiedDocumentation{doc, n});
2072 } else if (CodeParser::isWorthWarningAbout(doc)) {
2073 bool future = false;
2074 if (doc.metaCommandsUsed().contains(COMMAND_SINCE)) {
2075 QString sinceVersion = doc.metaCommandArgs(COMMAND_SINCE).at(0).first;
2076 if (getUnpatchedVersion(std::move(sinceVersion)) >
2077 getUnpatchedVersion(Config::instance().get(CONFIG_VERSION).asString()))
2078 future = true;
2079 }
2080 if (!future) {
2081 doc.location().warning(
2082 QStringLiteral("Cannot tie this documentation to anything"),
2083 QStringLiteral("qdoc found a /*! ... */ comment, but there was no "
2084 "topic command (e.g., '\\%1', '\\%2') in the "
2085 "comment and no function definition following "
2086 "the comment.")
2087 .arg(COMMAND_FN, COMMAND_PAGE));
2088 }
2089 }
2090 } else {
2091 parse_result.untied.emplace_back(UntiedDocumentation{doc, QStringList()});
2092
2093 CXCursor cur = clang_getCursor(tu, commentLoc);
2094 while (true) {
2095 CXCursorKind kind = clang_getCursorKind(cur);
2096 if (clang_isTranslationUnit(kind) || clang_isInvalid(kind))
2097 break;
2098 if (kind == CXCursor_Namespace) {
2099 parse_result.untied.back().context << fromCXString(clang_getCursorSpelling(cur));
2100 }
2101 cur = clang_getCursorLexicalParent(cur);
2102 }
2103 }
2104 }
2105
2106 clang_disposeTokens(tu, tokens, numTokens);
2107 m_namespaceScope.clear();
2108 s_fn.clear();
2109
2110 return parse_result;
2111}
2112
2113/*!
2114 Use clang to parse the function signature from a function
2115 command. \a location is used for reporting errors. \a fnSignature
2116 is the string to parse. It is always a function decl.
2117 \a idTag is the optional bracketed argument passed to \\fn, or
2118 an empty string.
2119 \a context is a string list representing the scope (namespaces)
2120 under which the function is declared.
2121
2122 Returns a variant that's either a Node instance tied to the
2123 function declaration, or a parsing failure for later processing.
2124 */
2125std::variant<Node*, FnMatchError> FnCommandParser::operator()(const Location &location, const QString &fnSignature,
2126 const QString &idTag, QStringList context)
2127{
2128 Node *fnNode = nullptr;
2129 /*
2130 If the \fn command begins with a tag, then don't try to
2131 parse the \fn command with clang. Use the tag to search
2132 for the correct function node. It is an error if it can
2133 not be found. Return 0 in that case.
2134 */
2135 if (!idTag.isEmpty()) {
2136 fnNode = m_qdb->findFunctionNodeForTag(idTag);
2137 if (!fnNode) {
2138 location.error(
2139 QStringLiteral("tag \\fn [%1] not used in any include file in current module").arg(idTag));
2140 } else {
2141 /*
2142 The function node was found. Use the formal
2143 parameter names from the \fn command, because
2144 they will be the names used in the documentation.
2145 */
2146 auto *fn = static_cast<FunctionNode *>(fnNode);
2147 QStringList leftParenSplit = fnSignature.mid(fnSignature.indexOf(fn->name())).split('(');
2148 if (leftParenSplit.size() > 1) {
2149 QStringList rightParenSplit = leftParenSplit[1].split(')');
2150 if (!rightParenSplit.empty()) {
2151 QString params = rightParenSplit[0];
2152 if (!params.isEmpty()) {
2153 QStringList commaSplit = params.split(',');
2154 Parameters &parameters = fn->parameters();
2155 if (parameters.count() == commaSplit.size()) {
2156 for (int i = 0; i < parameters.count(); ++i) {
2157 QStringList blankSplit = commaSplit[i].split(' ', Qt::SkipEmptyParts);
2158 if (blankSplit.size() > 1) {
2159 QString pName = blankSplit.last();
2160 // Remove any non-letters from the start of parameter name
2161 auto it = std::find_if(std::begin(pName), std::end(pName),
2162 [](const QChar &c) { return c.isLetter(); });
2163 parameters[i].setName(
2164 pName.remove(0, std::distance(std::begin(pName), it)));
2165 }
2166 }
2167 }
2168 }
2169 }
2170 }
2171 }
2172 return fnNode;
2173 }
2174 auto flags = static_cast<CXTranslationUnit_Flags>(CXTranslationUnit_Incomplete
2175 | CXTranslationUnit_SkipFunctionBodies
2176 | CXTranslationUnit_KeepGoing);
2177
2178 CompilationIndex index{ clang_createIndex(1, kClangDontDisplayDiagnostics) };
2179
2180 getDefaultArgs(m_defines, m_args);
2181
2182 if (m_pch) {
2183 m_args.push_back("-w");
2184 m_args.push_back("-include-pch");
2185 m_args.push_back((*m_pch).get().name.constData());
2186 }
2187
2188 TranslationUnit tu;
2189 QByteArray s_fn{};
2190 for (const auto &ns : std::as_const(context))
2191 s_fn.prepend("namespace " + ns.toUtf8() + " {");
2192 s_fn += fnSignature.toUtf8();
2193 if (!s_fn.endsWith(";"))
2194 s_fn += "{ }";
2195 s_fn.append(context.size(), '}');
2196
2197 const char *dummyFileName = fnDummyFileName;
2198 CXUnsavedFile unsavedFile { dummyFileName, s_fn.constData(),
2199 static_cast<unsigned long>(s_fn.size()) };
2200 CXErrorCode err = clang_parseTranslationUnit2(index, dummyFileName, m_args.data(),
2201 int(m_args.size()), &unsavedFile, 1, flags, &tu.tu);
2202 qCDebug(lcQdoc) << __FUNCTION__ << "clang_parseTranslationUnit2(" << dummyFileName << m_args
2203 << ") returns" << err;
2205 if (err || !tu) {
2206 location.error(QStringLiteral("clang could not parse \\fn %1").arg(fnSignature));
2207 return fnNode;
2208 } else {
2209 /*
2210 Always visit the tu if one is constructed, because
2211 it might be possible to find the correct node, even
2212 if clang detected diagnostics. Only bother to report
2213 the diagnostics if they stop us finding the node.
2214 */
2215 CXCursor cur = clang_getTranslationUnitCursor(tu);
2216 auto &config = Config::instance();
2217 ClangVisitor visitor(m_qdb, m_allHeaders, config.getInternalFilePatternsCompiled());
2218 bool ignoreSignature = false;
2219 visitor.visitFnArg(cur, &fnNode, ignoreSignature);
2220
2221 if (!fnNode) {
2222 unsigned diagnosticCount = clang_getNumDiagnostics(tu);
2223 const auto &config = Config::instance();
2224 if (diagnosticCount > 0 && (!config.preparing() || config.singleExec())) {
2225 return FnMatchError{ fnSignature, location };
2226 }
2227 }
2228 }
2229 return fnNode;
2230}
2231
2232QT_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:32
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:449
#define CONFIG_DOCUMENTATIONINHEADERS
Definition config.h:379
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:23
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