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
location.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
4#include "location.h"
5
6#include "config.h"
8
9#include <QtCore/qdebug.h>
10#include <QtCore/qdir.h>
11#include <QtCore/qfile.h>
12#include <QtCore/qregularexpression.h>
13#include <QtCore/qtextstream.h>
14
15#include <cstdio>
16#include <cstdlib>
17#include <utility>
18
19using namespace Qt::Literals::StringLiterals;
20
21QT_BEGIN_NAMESPACE
22
23int Location::s_tabSize;
24int Location::s_warningCount = 0;
25int Location::s_errorCount = 0;
26int Location::s_warningLimit = -1;
27QString Location::s_programName;
28QString Location::s_project;
29QString Location::s_projectRoot;
30QSet<QString> Location::s_reports;
31QSet<QString> Location::s_emittedWarnings;
32QRegularExpression *Location::s_spuriousRegExp = nullptr;
33std::unique_ptr<QFile> Location::s_warningLogFile;
34std::unique_ptr<QTextStream> Location::s_warningLogStream;
35
36/*!
37 \class Location
38
39 \brief The Location class provides a way to mark a location in a file.
40
41 It maintains a stack of file positions. A file position
42 consists of the file path, line number, and column number.
43 The location is used for printing error messages that are
44 tied to a location in a file.
45 */
46
47/*!
48 Constructs an empty location.
49 */
50Location::Location() : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
51{
52 // nothing.
53}
54
55/*!
56 Constructs a location with (fileName, 1, 1) on its file
57 position stack.
58 */
59Location::Location(const QString &fileName)
60 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
61{
62 push(fileName);
63}
64
65/*!
66 The copy constructor copies the contents of \a other into
67 this Location using the assignment operator.
68 */
70 : m_stk(nullptr), m_stkTop(&m_stkBottom), m_stkDepth(0), m_etc(false)
71{
72 *this = other;
73}
74
75/*!
76 The assignment operator does a deep copy of the entire
77 state of \a other into this Location.
78 */
80{
81 if (this == &other)
82 return *this;
83
84 QStack<StackEntry> *oldStk = m_stk;
85
86 m_stkBottom = other.m_stkBottom;
87 if (other.m_stk == nullptr) {
88 m_stk = nullptr;
89 m_stkTop = &m_stkBottom;
90 } else {
91 m_stk = new QStack<StackEntry>(*other.m_stk);
92 m_stkTop = &m_stk->top();
93 }
94 m_stkDepth = other.m_stkDepth;
95 m_etc = other.m_etc;
96 delete oldStk;
97 return *this;
98}
99
100/*!
101 Returns \c true if this instance points to the
102 same location as \a other.
103
104 Two locations are considered equal if both the
105 filePath() and lineNo() are equal, regardless of
106 stack depth.
107 */
108bool Location::operator==(const Location &other) const
109{
110 if (&other == this)
111 return true;
112
113 if (lineNo() != other.lineNo())
114 return false;
115
116 if (filePath() != other.filePath())
117 return false;
118
119 return true;
120}
121
122/*!
123 Returns \c true if this instance does not point to the
124 same location as \a other.
125 */
126bool Location::operator!=(const Location &other) const
127{
128 return !(*this == other);
129}
130
131/*!
132 If the file position on top of the stack has a line number
133 less than 1, set its line number to 1 and its column number
134 to 1. Otherwise, do nothing.
135 */
137{
138 if (m_stkTop->m_lineNo < 1) {
139 m_stkTop->m_lineNo = 1;
140 m_stkTop->m_columnNo = 1;
141 }
142}
143
144/*!
145 Advance the current file position, using \a ch to decide how to do
146 that. If \a ch is a \c{'\\n'}, increment the current line number and
147 set the column number to 1. If \ch is a \c{'\\t'}, increment to the
148 next tab column. Otherwise, increment the column number by 1.
149
150 The current file position is the one on top of the position stack.
151 */
152void Location::advance(QChar ch)
153{
154 if (ch == QLatin1Char('\n')) {
155 m_stkTop->m_lineNo++;
156 m_stkTop->m_columnNo = 1;
157 } else if (ch == QLatin1Char('\t')) {
158 m_stkTop->m_columnNo = 1 + s_tabSize * (m_stkTop->m_columnNo + s_tabSize - 1) / s_tabSize;
159 } else {
160 m_stkTop->m_columnNo++;
161 }
162}
163
164/*!
165 Pushes \a filePath onto the file position stack. The current
166 file position becomes (\a filePath, 1, 1).
167
168 \sa pop()
169*/
170void Location::push(const QString &filePath)
171{
172 if (m_stkDepth++ >= 1) {
173 if (m_stk == nullptr)
174 m_stk = new QStack<StackEntry>;
175 m_stk->push(StackEntry());
176 m_stkTop = &m_stk->top();
177 }
178
179 m_stkTop->m_filePath = filePath;
180 m_stkTop->m_lineNo = INT_MIN;
181 m_stkTop->m_columnNo = 1;
182}
183
184/*!
185 Pops the top of the internal stack. The current file position
186 becomes the next one in the new top of stack.
187
188 \sa push()
189*/
191{
192 if (--m_stkDepth == 0) {
193 m_stkBottom = StackEntry();
194 } else {
195 if (!m_stk)
196 return;
197 m_stk->pop();
198 if (m_stk->isEmpty()) {
199 delete m_stk;
200 m_stk = nullptr;
201 m_stkTop = &m_stkBottom;
202 } else {
203 m_stkTop = &m_stk->top();
204 }
205 }
206}
207
208/*! \fn bool Location::isEmpty() const
209
210 Returns \c true if there is no file name set yet; returns \c false
211 otherwise. The functions filePath(), lineNo() and columnNo()
212 must not be called on an empty Location object.
213 */
214
215/*! \fn const QString &Location::filePath() const
216 Returns the current path and file name. If the Location is
217 empty, the returned string is null.
218
219 \sa lineNo(), columnNo()
220 */
221
222/*!
223 Returns the file name part of the file path, ie the current
224 file. Returns an empty string if the file path is empty.
225 */
227{
228 QFileInfo fi(filePath());
229 return fi.fileName();
230}
231
232/*!
233 Returns the suffix of the file name. Returns an empty string
234 if the file path is empty.
235 */
237{
238 QString fp = filePath();
239 return (fp.isEmpty() ? fp : fp.mid(fp.lastIndexOf('.') + 1));
240}
241
242/*! \fn int Location::lineNo() const
243 Returns the current line number.
244 Must not be called on an empty Location object.
245
246 \sa filePath(), columnNo()
247*/
248
249/*! \fn int Location::columnNo() const
250 Returns the current column number.
251 Must not be called on an empty Location object.
252
253 \sa filePath(), lineNo()
254*/
255
256/*!
257 Writes \a message and \a details to stderr as a formatted
258 warning message. Does not write the message if qdoc is in
259 the Prepare phase.
260
261 A warning whose source location (including the include
262 stack), project, message, and details are all identical to
263 one already reported in this project run is not emitted
264 again. The warning is thereby logged and counted only once.
265 */
266void Location::warning(const QString &message, const QString &details) const
267{
268 const auto &config = Config::instance();
269 if (!config.preparing() || config.singleExec())
270 emitMessage(Warning, message, details);
271}
272
273/*!
274 Writes \a message and \a details to stderr as a formatted
275 error message. Does not write the message if qdoc is in
276 the Prepare phase.
277 */
278void Location::error(const QString &message, const QString &details) const
279{
280 const auto &config = Config::instance();
281 if (!config.preparing() || config.singleExec())
282 emitMessage(Error, message, details);
283}
284
285/*!
286 Records a failure that must make the process exit non-zero, independently
287 of the warning limit. The count persists across qdocconf files in one run.
288 */
290{
291 ++s_errorCount;
292}
293
294/*!
295 Returns the error code QDoc should exit with. A recorded failure takes
296 precedence over the warning limit.
297 */
299{
300 if (s_warningLimit >= 0 && s_warningCount > s_warningLimit) {
301 Location().emitMessage(
302 Error,
303 QStringLiteral("Documentation warnings (%1) exceeded the limit (%2) for '%3'.")
304 .arg(QString::number(s_warningCount), QString::number(s_warningLimit),
305 s_project),
306 QString());
307 if (s_errorCount == 0)
308 return s_warningCount;
309 }
310
311 return s_errorCount > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
312}
313
314/*!
315 Writes \a message and \a details to stderr as a formatted
316 error message and then exits the program. qdoc prints fatal
317 errors in either phase (Prepare or Generate).
318 */
319void Location::fatal(const QString &message, const QString &details) const
320{
321 emitMessage(Error, message, details);
322 information(message);
323 information(details);
324 information("Aborting");
325 exit(EXIT_FAILURE);
326}
327
328/*!
329 Writes \a message and \a details to \c{stderr} as an informational
330 report. Includes the project name and, when available, the file
331 name and line number.
332
333 Suppresses repeated messages at the same file and line during a
334 QDoc run. Reports without a source location are deduplicated
335 separately by message.
336
337 Emits reports only in the \e generate or \e{single-exec} phase.
338 Reports do not count toward the warning limit or affect the exit
339 code.
340 */
341void Location::report(const QString &message, const QString &details) const
342{
343 const auto &config = Config::instance();
344 if (!config.preparing() || config.singleExec()) {
345 // Separate the fields to avoid ambiguous deduplication keys.
346 // Produces e.g. /path/file.qdoc<NUL>33<NUL>Missing alt text
347 QString key = message;
348 if (!isEmpty())
349 key = filePath() + QChar(0) + QString::number(lineNo()) + QChar(0) + message;
350 if (!s_reports.contains(key)) {
351 emitMessage(Report, message, details);
352 s_reports.insert(key);
353 }
354 }
355}
356
357/*!
358 Gets several parameters from the config, including
359 tab size, program name, and a regular expression that
360 appears to be used for matching certain error messages
361 so that emitMessage() can avoid printing them.
362 */
364{
365 Config &config = Config::instance();
366 s_tabSize = config.get(CONFIG_TABSIZE).asInt();
367 s_programName = config.programName();
368 s_project = config.get(CONFIG_PROJECT).asString();
369
370 // Initialize project root for relative path calculation with priority:
371 // 1. QDOC_PROJECT_ROOT environment variable (highest priority)
372 // 2. projectroot configuration variable (fallback)
373 // 3. Leave empty if neither available (absolute paths)
374 QString qdocProjectRoot = qEnvironmentVariable("QDOC_PROJECT_ROOT");
375 if (qdocProjectRoot.isNull())
376 qdocProjectRoot = config.get(CONFIG_PROJECTROOT).asString();
377
378 if (!qdocProjectRoot.isEmpty() && QDir(qdocProjectRoot).exists())
379 s_projectRoot = QDir::cleanPath(qdocProjectRoot);
380
381 if (!config.singleExec())
382 s_warningCount = 0;
383 s_emittedWarnings.clear();
384 if (qEnvironmentVariableIsSet("QDOC_ENABLE_WARNINGLIMIT")
385 || config.get(CONFIG_WARNINGLIMIT + Config::dot + "enabled").asBool())
386 s_warningLimit = config.get(CONFIG_WARNINGLIMIT).asInt();
387
388 QRegularExpression regExp = config.getRegExp(CONFIG_SPURIOUS);
389 if (regExp.isValid()) {
390 s_spuriousRegExp = new QRegularExpression(regExp);
391 } else {
392 config.get(CONFIG_SPURIOUS).location()
393 .warning(QStringLiteral("Invalid regular expression '%1'")
394 .arg(regExp.pattern()));
395 }
396
397 if (config.get(CONFIG_LOGWARNINGS).asBool())
398 initializeWarningLog(config);
399}
400
401/*!
402 Creates the header for the warning log file.
403*/
404QString Location::warningLogHeader()
405{
406 const auto &config = Config::instance();
407 QStringList lines;
408
409 lines << "# QDoc Warning Log"_L1;
410 lines << "# Project: "_L1 + s_project;
411
412 // Add command line arguments unless disabled
413 if (!config.get(CONFIG_LOGWARNINGSDISABLECLIARGS).asBool()) {
414 const QStringList args = QCoreApplication::arguments();
415 if (!args.isEmpty()) {
416 QStringList quotedArgs;
417 for (const QString &arg : args) {
418 // Quote arguments containing spaces
419 if (arg.contains(QLatin1Char(' '))) {
420 quotedArgs << '"'_L1 + arg + '"'_L1;
421 } else {
422 quotedArgs << arg;
423 }
424 }
425 lines << "# Command: "_L1 + quotedArgs.join(QLatin1Char(' '));
426 }
427 }
428
429 if (!s_projectRoot.isEmpty()) {
430 // Indicate which method was used to determine project root
431 if (!qEnvironmentVariable("QDOC_PROJECT_ROOT").isNull()) {
432 lines << "# Root: QDOC_PROJECT_ROOT"_L1;
433 } else {
434 lines << "# Root: projectroot config"_L1;
435 }
436 lines << "# Path-Format: relative"_L1;
437 } else {
438 lines << "# Path-Format: absolute"_L1;
439 }
440
441 lines << "#"_L1;
442 return lines.join('\n'_L1);
443}
444
445/*!
446 Formats a file path for the warning log, converting to relative path
447 if a project root is configured.
448 */
449QString Location::formatPathForWarningLog(const QString &path)
450{
451 if (s_projectRoot.isEmpty())
452 return path;
453
454 QDir projectDir(s_projectRoot);
455 QString relativePath = projectDir.relativeFilePath(path);
456
457 // Only use relative path if it doesn't go outside the project root
458 if (!relativePath.startsWith("../"_L1))
459 return relativePath;
460
461 return path;
462}
463
464/*!
465 Initializes the warning log file, creates the output directory if needed, and
466 adds the command-line arguments to QDoc to the top of the log file.
467
468 This function assumes that log warnings are enabled and should only be
469 called when \c {CONFIG_LOGWARNINGS} is \c {true}.
470 */
471void Location::initializeWarningLog(const Config &config)
472{
473 const QString logFileName = s_project + "-qdoc-warnings.log";
474 const QString &outputDir = config.getOutputDir();
475
476 const OutputDirectory dir =
477 OutputDirectory::ensure(outputDir, Location());
478
479 const QString &logFilePath = dir.absoluteFilePath(logFileName);
480
481 s_warningLogFile = std::make_unique<QFile>(logFilePath);
482 if (s_warningLogFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
483 s_warningLogStream = std::make_unique<QTextStream>(s_warningLogFile.get());
484
485 *s_warningLogStream << warningLogHeader() << Qt::endl;
486 *s_warningLogStream << Qt::endl;
487 } else {
488 Location().warning(QStringLiteral("Failed to open warning log file: %1").arg(logFilePath));
489 s_warningLogFile.reset();
490 }
491}
492
493/*!
494 Writes the message \a formattedMessage to the warning log file if it is
495 enabled and the message type \a type is a warning.
496 */
497void Location::writeToWarningLog(MessageType type, const QString &formattedMessage)
498{
499 if (type != Warning || !s_warningLogStream)
500 return;
501
502 *s_warningLogStream << formattedMessage << Qt::endl;
503}
504
505/*!
506 Apparently, all this does is delete the regular expression
507 used for intercepting certain error messages that should
508 not be emitted by emitMessage().
509 */
511{
512 delete s_spuriousRegExp;
513 s_spuriousRegExp = nullptr;
514
515 s_emittedWarnings.clear();
516
517 s_warningLogStream.reset();
518 s_warningLogFile.reset();
519}
520
521/*!
522 Prints \a message to \c stdout followed by a \c{'\n'}.
523 */
524void Location::information(const QString &message)
525{
526 printf("%s\n", message.toLatin1().data());
527 fflush(stdout);
528}
529
530/*!
531 Report a program bug, including the \a hint.
532 */
533void Location::internalError(const QString &hint)
534{
535 Location().fatal(QStringLiteral("Internal error (%1)").arg(hint),
536 QStringLiteral("There is a bug in %1. Seek advice from your local"
537 " %2 guru.")
538 .arg(s_programName, s_programName));
539}
540
541/*!
542 Formats \a message and \a details into a single string
543 and outputs that string to \c stderr. \a type specifies
544 whether the \a message is an error, warning, or report.
545
546 Each output format validates the same content in one project run, so an
547 identical warning can be encountered once per format. Only the first
548 encounter is emitted, logged, and counted. The key includes the complete
549 location string (including the include stack), the project, the message,
550 and the details. These fields are separated by \c{QChar(0)} to avoid
551 ambiguous boundaries (cf. report()). Errors and reports are never
552 suppressed.
553 */
554void Location::emitMessage(MessageType type, const QString &message, const QString &details) const
555{
556 if (type == Warning && s_spuriousRegExp != nullptr) {
557 auto match = s_spuriousRegExp->match(message, 0, QRegularExpression::NormalMatch,
558 QRegularExpression::AnchorAtOffsetMatchOption);
559 if (match.hasMatch() && match.capturedLength() == message.size())
560 return;
561 }
562
563 if (type == Warning) {
564 const QString key = toString() + QChar(0) + s_project + QChar(0)
565 + message + QChar(0) + details;
566 if (s_emittedWarnings.contains(key))
567 return;
568 s_emittedWarnings.insert(key);
569 }
570
571 QString result = message;
572 if (!details.isEmpty())
573 result += "\n[" + details + QLatin1Char(']');
574 result.replace("\n", "\n ");
575 if (isEmpty()) {
576 if (type == Error)
577 result.prepend(QStringLiteral(": error: "));
578 else if (type == Warning) {
579 result.prepend(QStringLiteral(": warning: "));
580 ++s_warningCount;
581 } else if (type == Report)
582 result.prepend("qdoc: '%1': "_L1.arg(s_project));
583 } else {
584 if (type == Error)
585 result.prepend(": [%1] (qdoc) error: "_L1.arg(s_project));
586 else if (type == Warning) {
587 result.prepend(": [%1] (qdoc) warning: "_L1.arg(s_project));
588 ++s_warningCount;
589 } else if (type == Report)
590 result.prepend(": [%1] (qdoc) report: "_L1.arg(s_project));
591 }
592 if (type != Report || !isEmpty())
593 result.prepend(toString());
594 fprintf(stderr, "%s\n", result.toLatin1().data());
595 fflush(stderr);
596
597 // Create a version with relative paths for the warning log
598 QString logMessage = std::move(result);
599 if (type == Warning && !s_projectRoot.isEmpty()) {
600 // Replace the absolute path portion with a relative path for log file
601 QString locationString = toString();
602 QString formattedLocationString = formatPathForWarningLog(locationString);
603 logMessage.replace(locationString, formattedLocationString);
604 }
605 writeToWarningLog(type, logMessage);
606}
607
608/*!
609 Converts the location to a string to be prepended to error
610 messages.
611 */
613{
614 QString str;
615
616 if (isEmpty()) {
617 str = s_programName;
618 } else {
619 Location loc2 = *this;
620 loc2.setEtc(false);
621 loc2.pop();
622 if (!loc2.isEmpty()) {
623 QString blah = QStringLiteral("In file included from ");
624 for (;;) {
625 str += blah;
626 str += loc2.top();
627 loc2.pop();
628 if (loc2.isEmpty())
629 break;
630 str += QStringLiteral(",\n");
631 blah.fill(' ');
632 }
633 str += QStringLiteral(":\n");
634 }
635 str += top();
636 }
637 return str;
638}
639
640QString Location::top() const
641{
642 QDir path(filePath());
643 QString str = path.absolutePath();
644 if (lineNo() >= 1) {
645 str += QLatin1Char(':');
646 str += QString::number(lineNo());
647 }
648 if (etc())
649 str += QLatin1String(" (etc.)");
650 return str;
651}
652
653QT_END_NAMESPACE
The Config class contains the configuration variables for controlling how qdoc produces documentation...
Definition config.h:95
bool singleExec() const
Definition config.h:472
The Location class provides a way to mark a location in a file.
Definition location.h:20
QString fileName() const
Returns the file name part of the file path, ie the current file.
Definition location.cpp:226
void fatal(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted error message and then exits the program.
Definition location.cpp:319
QString fileSuffix() const
Returns the suffix of the file name.
Definition location.cpp:236
bool operator==(const Location &other) const
Returns true if this instance points to the same location as other.
Definition location.cpp:108
Location(const Location &other)
The copy constructor copies the contents of other into this Location using the assignment operator.
Definition location.cpp:69
void error(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted error message.
Definition location.cpp:278
int lineNo() const
Returns the current line number.
Definition location.h:50
static int exitCode()
Returns the error code QDoc should exit with.
Definition location.cpp:298
void report(const QString &message, const QString &details=QString()) const
Writes message and details to {stderr} as an informational report.
Definition location.cpp:341
QString toString() const
Converts the location to a string to be prepended to error messages.
Definition location.cpp:612
bool operator!=(const Location &other) const
Returns true if this instance does not point to the same location as other.
Definition location.cpp:126
static void initialize()
Gets several parameters from the config, including tab size, program name, and a regular expression t...
Definition location.cpp:363
Location()
Constructs an empty location.
Definition location.cpp:50
void push(const QString &filePath)
Pushes filePath onto the file position stack.
Definition location.cpp:170
void start()
If the file position on top of the stack has a line number less than 1, set its line number to 1 and ...
Definition location.cpp:136
void warning(const QString &message, const QString &details=QString()) const
Writes message and details to stderr as a formatted warning message.
Definition location.cpp:266
void setEtc(bool etc)
Definition location.h:41
void advance(QChar ch)
Advance the current file position, using ch to decide how to do that.
Definition location.cpp:152
static void increaseErrorCount()
Records a failure that must make the process exit non-zero, independently of the warning limit.
Definition location.cpp:289
Location & operator=(const Location &other)
The assignment operator does a deep copy of the entire state of other into this Location.
Definition location.cpp:79
bool isEmpty() const
Returns true if there is no file name set yet; returns false otherwise.
Definition location.h:45
bool etc() const
Definition location.h:52
static void terminate()
Apparently, all this does is delete the regular expression used for intercepting certain error messag...
Definition location.cpp:510
void pop()
Pops the top of the internal stack.
Definition location.cpp:190
Location(const QString &filePath)
Constructs a location with (fileName, 1, 1) on its file position stack.
Definition location.cpp:59
Represents an output directory that has been verified to exist.
#define CONFIG_TABSIZE
Definition config.h:455
#define CONFIG_LOGWARNINGSDISABLECLIARGS
Definition config.h:424
#define CONFIG_WARNINGLIMIT
Definition config.h:470
#define CONFIG_LOGWARNINGS
Definition config.h:423
#define CONFIG_PROJECT
Definition config.h:438
#define CONFIG_PROJECTROOT
Definition config.h:439
#define CONFIG_SPURIOUS
Definition config.h:452